home *** CD-ROM | disk | FTP | other *** search
/ Usenet 1993 July / InfoMagic USENET CD-ROM July 1993.ISO / sources / unix / volume25 / trn / part10 < prev    next >
Encoding:
Internet Message Format  |  1991-12-02  |  72.0 KB

  1. Subject:  v25i013:  trn 2.0 - threaded newsreader based on rn 4.4, Part10/13
  2. Newsgroups: comp.sources.unix
  3. Approved: vixie@pa.dec.com
  4.  
  5. Submitted-by: davison@borland.com (Wayne Davison)
  6. Posting-number: Volume 25, Issue 13
  7. Archive-name: trn/part10
  8.  
  9. #! /bin/sh
  10. # This is a shell archive.  Remove anything before this line, then unpack
  11. # it by saving it into a file and typing "sh file".  To overwrite existing
  12. # files, type "sh file -c".  You can also feed this as standard input via
  13. # unshar, or by typing "sh <file", e.g..  If this archive is complete, you
  14. # will see the following message at the end:
  15. #        "End of archive 10 (of 13)."
  16. # Contents:  mthreads.c ng.c
  17. # Wrapped by vixie@cognition.pa.dec.com on Tue Dec  3 16:34:56 1991
  18. PATH=/bin:/usr/bin:/usr/ucb ; export PATH
  19. if test -f 'mthreads.c' -a "${1}" != "-c" ; then 
  20.   echo shar: Will not clobber existing file \"'mthreads.c'\"
  21. else
  22. echo shar: Extracting \"'mthreads.c'\" \(33893 characters\)
  23. sed "s/^X//" >'mthreads.c' <<'END_OF_FILE'
  24. X/* $Id: mthreads.c,v 4.4.3.1 1991/11/22 04:12:15 davison Trn $
  25. X**
  26. X** $Log: mthreads.c,v $
  27. X** Revision 4.4.3.1  1991/11/22  04:12:15  davison
  28. X** Trn Release 2.0
  29. X** 
  30. X*/
  31. X
  32. X/* mthreads.c -- for making and updating a discussion-thread database
  33. X**
  34. X** We use the active file to read the high/low counts for each newsgroup
  35. X** and compare them with the corresponding values in a file called active2
  36. X** (which we created to keep the database high/lows in one place).  If they
  37. X** don't match, we read/update/write the group's thread file and move on.
  38. X** If the active2 file is missing or corrupted, it will be repaired in the
  39. X** normal course of operation.
  40. X**
  41. X** Usage:  mthreads [-d[MM]] [-e[HHMM]] [-s[hsec]] [-aDfknv] [hierarchy_list]
  42. X*/
  43. X
  44. X#include "EXTERN.h"
  45. X#include "common.h"
  46. X#include "threads.h"
  47. X#ifdef SERVER
  48. X#include "server.h"
  49. X#endif
  50. X#include "INTERN.h"
  51. X#include "mthreads.h"
  52. X
  53. X#ifdef TZSET
  54. X#include <time.h>
  55. X#else
  56. X#include <sys/time.h>
  57. X#include <sys/timeb.h>
  58. X#endif
  59. X
  60. X#if !defined(FTRUNCATE) && !defined(CHSIZE)
  61. X# ifdef F_FREESP
  62. X#  define MYCHSIZE
  63. X# else
  64. X#  define MVTRUNC
  65. X# endif
  66. X#endif
  67. X
  68. X#ifdef USESYSLOG
  69. X#include <syslog.h>
  70. X#else
  71. XFILE *fp_log;
  72. X#endif
  73. X
  74. XFILE *fp_tmp, *fp_active, *fp_active2, *fp_active2w = Nullfp;
  75. bool eof_active = FALSE, eof_active2 = FALSE;
  76. long first, last, first2, last2;
  77. char ch, ch2;
  78. X
  79. struct stat filestat;
  80. X
  81. char buf[LBUFLEN+1];
  82. X
  83. static char line[256];
  84. static char line2[256];
  85. X
  86. char fmt_active2[] = "%s %010ld %07ld %c\n";
  87. X
  88. char *filename;
  89. X
  90. typedef struct _active_line {
  91. X    struct _active_line *link;
  92. X    char *name;
  93. X    long last;
  94. X    long first;
  95. X    char type;
  96. X} ACTIVE_LINE;
  97. X
  98. X#define Nullact Null(ACTIVE_LINE*)
  99. X
  100. ACTIVE_LINE *line_root = Nullact, *last_line = Nullact, *pline = Nullact;
  101. X
  102. bool force_flag = FALSE, kill_mthreads = FALSE, no_processing = FALSE;
  103. bool add_new = FALSE, rebuild = FALSE, zap_thread = FALSE;
  104. bool acttimes_flag = FALSE, grevious_error;
  105. bool initializing = TRUE;
  106. int daemon_delay = 0, log_verbosity = 0, debug = 0, slow_down = 0;
  107. long expire_time = 0;
  108. char *hierarchy_list = NULL;
  109. long truncate_len = -1;
  110. X
  111. char nullstr[1] = "";
  112. X
  113. extern int locked, cron_locking;
  114. X
  115. X#ifdef TZSET
  116. time_t tnow;
  117. X#else
  118. struct timeb ftnow;
  119. X#endif
  120. X
  121. X#define TIMER_FIRST 1
  122. X#define TIMER_DEFAULT (10 * 60)
  123. X
  124. int processed_groups = 0, added_groups = 0, removed_groups = 0;
  125. int action;
  126. X
  127. X#define NG_DEFAULT    0
  128. X#define NG_MATCH    1
  129. X#define NG_SKIP        2
  130. X
  131. X#ifdef SERVER
  132. char *server;
  133. X#else
  134. time_t last_modified = 0;
  135. X#endif
  136. X
  137. SIGRET alarm_handler(), int_handler();
  138. bool makethreads ANSI((void));
  139. void log_startup ANSI((void));
  140. void log_stats ANSI((void));
  141. X
  142. int
  143. main(argc, argv)
  144. int  argc;
  145. char *argv[];
  146. X{
  147. X    int fd;
  148. X    long pid;
  149. X
  150. X    while (--argc) {
  151. X    if (**++argv == '-') {
  152. X        while (*++*argv) {
  153. X        switch (**argv) {
  154. X        case 'a':        /* automatically thread new groups */
  155. X            add_new = TRUE;
  156. X            break;
  157. X        case 'c':        /* continue trying to lock */
  158. X            cron_locking = TRUE;
  159. X            break;
  160. X        case 'D':        /* run in debug mode */
  161. X            debug++;
  162. X            break;
  163. X        case 'd':        /* run in daemon mode */
  164. X            if (*++*argv <= '9' && **argv >= '0') {
  165. X            daemon_delay = atoi(*argv) * 60;
  166. X            while (*++*argv <= '9' && **argv >= '0') {
  167. X                ;
  168. X            }
  169. X            } else {
  170. X            daemon_delay = TIMER_DEFAULT;
  171. X            }
  172. X            --*argv;
  173. X            break;
  174. X        case 'e': {        /* enhanced expire processing */
  175. X            struct tm *ts;
  176. X            long desired;
  177. X
  178. X            (void) time(&expire_time);
  179. X            ts = localtime(&expire_time);
  180. X
  181. X            if (*++*argv <= '9' && **argv >= '0') {
  182. X            desired = atol(*argv);
  183. X            if (desired/100 > 23 || desired%100 > 59) {
  184. X                fprintf(stderr, "Illegal expire time: '%04d'\n",
  185. X                desired);
  186. X                exit(1);
  187. X            }
  188. X            desired = (desired/100)*60 + desired%100;
  189. X            while (*++*argv <= '9' && **argv >= '0') {
  190. X                ;
  191. X            }
  192. X            } else {
  193. X            desired = 30;            /* 0030 = 12:30am */
  194. X            }
  195. X            --*argv;
  196. X            desired -= ts->tm_hour * 60 + ts->tm_min;
  197. X            if (desired < 0) {
  198. X            desired += 24 * 60;
  199. X            }
  200. X            expire_time += desired * 60 - ts->tm_sec;
  201. X            break;
  202. X         }
  203. X        case 'f':        /* force each group to process */
  204. X            force_flag = TRUE;
  205. X            break;
  206. X        case 'k':        /* kill running mthreads */
  207. X            kill_mthreads = TRUE;
  208. X            break;
  209. X        case 'n':        /* don't process anything */
  210. X            no_processing = TRUE;
  211. X            break;
  212. X        case 's':        /* sleep between articles */
  213. X            if (*++*argv <= '9' && **argv >= '0') {
  214. X            slow_down = atoi(*argv);
  215. X            while (*++*argv <= '9' && **argv >= '0') {
  216. X                ;
  217. X            }
  218. X            } else {
  219. X            slow_down = 1L * 1000 * 1000;
  220. X            }
  221. X            --*argv;
  222. X            break;
  223. X        case 't':        /* maintain active.times file */
  224. X#ifdef ACTIVE_TIMES
  225. X            if (strEQ(ACTIVE_TIMES, "nntp")) {
  226. X            fprintf(stderr, "Ignoring the -t option.\n");
  227. X            } else {
  228. X            acttimes_flag = TRUE;
  229. X            }
  230. X#else
  231. X            fprintf(stderr, "Ignoring the -t option.\n");
  232. X#endif
  233. X            break;
  234. X        case 'v':        /* get more verbose in the log file */
  235. X            log_verbosity++;
  236. X            break;
  237. X        case 'z':        /* destroy .thread on severe signal */
  238. X            zap_thread = TRUE;
  239. X            break;
  240. X        default:
  241. X            fprintf(stderr, "Unknown option: '%c'\n", **argv);
  242. X            exit(1);
  243. X        }
  244. X        }
  245. X    } else {
  246. X        if (hierarchy_list) {
  247. X        fprintf(stderr, "Specify the newsgroups in one comma-separated list.\n");
  248. X        exit(1);
  249. X        }
  250. X        hierarchy_list = *argv;
  251. X    }
  252. X    }
  253. X
  254. X    /* Initialize umask(), file_exp(), etc. */
  255. X    mt_init();
  256. X
  257. X    /* If this is a kill request, look for the daemon lock file. */
  258. X    if (kill_mthreads) {
  259. X    if (!mt_lock(DAEMON_LOCK, SIGTERM)) {
  260. X        fprintf(stderr, "No mthreads daemon is running.\n");
  261. X        wrap_it_up(1);
  262. X    }
  263. X    fprintf(stderr, "Killed mthreads daemon.\n");
  264. X    wrap_it_up(0);
  265. X    }
  266. X
  267. X    /* See if an mthreads pass is already going. */
  268. X    if (mt_lock(PASS_LOCK, 0) != 0 && !daemon_delay) {
  269. X    fprintf(stderr, "mthreads is already running.\n");
  270. X    wrap_it_up(1);
  271. X    }
  272. X
  273. X#ifdef USESYSLOG
  274. X# ifdef LOG_DAEMON
  275. X    openlog("mthreads", LOG_PID, USESYSLOG);
  276. X# else
  277. X    openlog("mthreads", LOG_PID);
  278. X# endif
  279. X#else
  280. X    /* Open our log file */
  281. X    filename = file_exp(MTLOG);
  282. X    if ((fp_log = fopen(filename, "a")) == Nullfp) {
  283. X    fprintf(stderr, "Unable to open `%s'.\n", filename);
  284. X    wrap_it_up(1);
  285. X    }
  286. X#endif
  287. X
  288. X#ifdef SIGHUP
  289. X    if (sigset(SIGHUP, SIG_IGN) != SIG_IGN) {
  290. X    sigset(SIGHUP, int_handler);
  291. X    }
  292. X#endif
  293. X    if (sigset(SIGINT, SIG_IGN) != SIG_IGN) {
  294. X    sigset(SIGINT, int_handler);
  295. X    }
  296. X#ifdef SIGQUIT
  297. X    if (sigset(SIGQUIT, SIG_IGN) != SIG_IGN) {
  298. X    sigset(SIGQUIT, int_handler);
  299. X    }
  300. X#endif
  301. X    sigset(SIGTERM, int_handler);
  302. X#ifdef SIGBUS
  303. X    sigset(SIGBUS, int_handler);
  304. X#endif
  305. X    sigset(SIGSEGV, int_handler);
  306. X#ifdef SIGTTIN
  307. X    sigset(SIGTTIN, SIG_IGN);
  308. X    sigset(SIGTTOU, SIG_IGN);
  309. X#endif
  310. X    sigset(SIGALRM, SIG_IGN);
  311. X#ifdef SERVER
  312. X    sigset(SIGPIPE, int_handler);
  313. X#endif
  314. X#ifdef lint
  315. X    alarm_handler();            /* foolishness for lint's sake */
  316. X    int_handler(SIGINT);
  317. X#endif
  318. X
  319. X    /* Ensure this machine has the right byte-order for the database */
  320. X    filename = file_exp(DBINIT);
  321. X    if ((fp_tmp = fopen(filename, FOPEN_RB)) == Nullfp
  322. X     || fread((char*)&mt_bmap,1,sizeof (BMAP), fp_tmp) < sizeof (BMAP)-1) {
  323. X    if (fp_tmp != Nullfp) {
  324. X        fclose(fp_tmp);
  325. X    }
  326. X     write_db_init:
  327. X    mybytemap(&mt_bmap);
  328. X    if ((fp_tmp = fopen(filename, FOPEN_WB)) == Nullfp) {
  329. X        log_entry("Unable to create file: `%s'.\n", filename);
  330. X        wrap_it_up(1);
  331. X    }
  332. X    mt_bmap.version = DB_VERSION;
  333. X    fwrite((char*)&mt_bmap, 1, sizeof (BMAP), fp_tmp);
  334. X    fclose(fp_tmp);
  335. X    } else {
  336. X    int i;
  337. X
  338. X    fclose(fp_tmp);
  339. X    if (mt_bmap.version != DB_VERSION) {
  340. X        if (mt_bmap.version == DB_VERSION-1) {
  341. X        rebuild = TRUE;
  342. X        log_entry("Upgrading database to version %d.\n", DB_VERSION);
  343. X        goto write_db_init;
  344. X        }
  345. X        log_entry("** Database is not the right version (%d instead of %d) **\n",
  346. X        mt_bmap.version, DB_VERSION);
  347. X        wrap_it_up(1);
  348. X    }
  349. X    mybytemap(&my_bmap);
  350. X    for (i = 0; i < sizeof (LONG); i++) {
  351. X        if (my_bmap.l[i] != mt_bmap.l[i]
  352. X         || (i < sizeof (WORD) && my_bmap.w[i] != mt_bmap.w[i])) {
  353. X        log_entry("\
  354. X** Byte-order conflict -- re-run from a compatible machine **\n\
  355. X\t\tor remove the current thread files, including db.init **\n");
  356. X        wrap_it_up(1);
  357. X        }
  358. X    }
  359. X    }
  360. X
  361. X#ifdef SERVER
  362. X    if ((server = get_server_name(0)) == NULL) {
  363. X    log_entry("Couldn't find name of news server.\n");
  364. X    wrap_it_up(1);
  365. X    }
  366. X#endif
  367. X
  368. X    initializing = FALSE;
  369. X
  370. X    /* If we're not in daemon mode, run through once and quit. */
  371. X    if (!daemon_delay) {
  372. X    log_startup();
  373. X    setbuf(stdout, Nullch);
  374. X    extra_expire = (expire_time != 0);
  375. X    makethreads();
  376. X    } else {
  377. X    cron_locking = FALSE;
  378. X    if (mt_lock(DAEMON_LOCK, 0) != 0) {
  379. X        fprintf(stderr, "An mthreads daemon is already running.\n");
  380. X        wrap_it_up(1);
  381. X    }
  382. X    /* For daemon mode, we cut ourself off from anything tty-related and
  383. X    ** run in the background (involves forks, but no knives).
  384. X    */
  385. X    close(0);
  386. X    if (open("/dev/null", 2) != 0) {
  387. X        fprintf(stderr, "unable to open /dev/null!\n");
  388. X        wrap_it_up(1);
  389. X    }
  390. X    close(1);
  391. X    close(2);
  392. X    dup(0);
  393. X    dup(0);
  394. X    while ((pid = fork()) < 0) {
  395. X        sleep(2);
  396. X    }
  397. X    if (pid) {
  398. X        exit(0);
  399. X    }
  400. X#ifdef TIOCNOTTY
  401. X    if ((fd = open("/dev/tty", 1)) >= 0) {
  402. X        ioctl(fd, TIOCNOTTY, (int*)0);
  403. X        close(fd);
  404. X    }
  405. X#else
  406. X    (void) setpgrp();
  407. X    while ((pid = fork()) < 0) {
  408. X        sleep(2);
  409. X    }
  410. X    if (pid) {
  411. X        exit(0);
  412. X    }
  413. X#endif
  414. X    /* Put our pid in the lock file for death detection */
  415. X    if ((fp_tmp = fopen(file_exp(MTDLOCK), "w")) != Nullfp) {
  416. X        fprintf(fp_tmp, "%ld\n", (long)getpid());
  417. X        fclose(fp_tmp);
  418. X    }
  419. X    log_startup();
  420. X
  421. X    sigset(SIGALRM, alarm_handler);
  422. X
  423. X    /* Start timer -- first interval is shorter than all others */
  424. X    alarm(TIMER_FIRST);
  425. X    for (;;) {
  426. X        pause();        /* let alarm go off */
  427. X        alarm(0);
  428. X
  429. X#ifndef USESYSLOG
  430. X        /* Re-open our log file, if needed */
  431. X        if (!fp_log && !(fp_log = fopen(file_exp(MTLOG), "a"))) {
  432. X        wrap_it_up(1);
  433. X        }
  434. X#endif
  435. X#ifndef SERVER
  436. X        if (stat(file_exp(ACTIVE), &filestat) < 0) {
  437. X        log_entry("Unable to stat active file -- quitting.\n");
  438. X        wrap_it_up(1);
  439. X        }
  440. X#endif
  441. X        if (expire_time && time(Null(time_t*)) > expire_time) {
  442. X        expire_time += 24L * 60 * 60;
  443. X        extra_expire = TRUE;
  444. X        }
  445. X#ifdef SERVER
  446. X        makethreads();    /* NNTP version always compares files */
  447. X#else
  448. X        if (extra_expire || filestat.st_mtime != last_modified) {
  449. X        last_modified = filestat.st_mtime;
  450. X        if (!makethreads()) {
  451. X            last_modified--;
  452. X        }
  453. X        }
  454. X#endif
  455. X        alarm(daemon_delay);
  456. X#ifndef USESYSLOG
  457. X        fclose(fp_log);    /* close the log file while we sleep */
  458. X        fp_log = Nullfp;
  459. X#endif
  460. X    } /* for */
  461. X    }/* if */
  462. X
  463. X    wrap_it_up(0);
  464. X    return 0;                /* NOTREACHED */
  465. X}
  466. X
  467. SIGRET
  468. alarm_handler()
  469. X{
  470. X    sigset(SIGALRM, alarm_handler);
  471. X}
  472. X
  473. SIGRET
  474. int_handler(sig)
  475. int sig;
  476. X{
  477. X    static int visits = 0;
  478. X    int ret = 0;
  479. X
  480. X    if (++visits > 4) {
  481. X    wrap_it_up(1);
  482. X    }
  483. X
  484. X#ifndef USESYSLOG
  485. X    /* Re-open our log file, if needed */
  486. X    if (fp_log || (fp_log = fopen(file_exp(MTLOG), "a")))
  487. X#endif
  488. X    {
  489. X    switch (sig) {
  490. X    case SIGTERM:
  491. X#ifdef SIGHUP
  492. X    case SIGHUP:
  493. X#endif
  494. X#ifdef SIGQUIT
  495. X    case SIGQUIT:
  496. X#endif
  497. X        log_entry("halt requested.\n");
  498. X        zap_thread = 0;
  499. X        break;
  500. X#ifdef SERVER
  501. X    case SIGPIPE:
  502. X        log_entry("broken pipe -- trying to continue.\n");
  503. X        sigset(SIGPIPE, int_handler);
  504. X        return;
  505. X#endif
  506. X#ifdef SIGBUS
  507. X        case SIGBUS:
  508. X#endif
  509. X        case SIGSEGV:
  510. X        log_error("** Severe signal: %d **\n", sig);
  511. X        /* Destroy offending thread file if requested to do so. */
  512. X        if (zap_thread) {
  513. X            unlink(thread_name(line));
  514. X            log_entry("Destroyed thread file for %s\n", line);
  515. X        }
  516. X        break;
  517. X    default:
  518. X        log_entry("interrupt %d received.\n", sig);
  519. X        zap_thread = 0;
  520. X        ret = 1;
  521. X        break;
  522. X    }
  523. X    }
  524. X    if (!daemon_delay) {
  525. X    printf("Interrupt %d!\n", sig);
  526. X    if (zap_thread) {
  527. X        printf("Destroyed thread file for %s\n", line);
  528. X    }
  529. X    }
  530. X
  531. X    /* If we're in the middle of writing the new active2 file, finish it. */
  532. X    if (fp_active2w) {
  533. X    if (*line2) {
  534. X        if (index(line2, ' ')) {
  535. X        fputs(line2, fp_active2w);
  536. X        } else {
  537. X        fprintf(fp_active2w, fmt_active2, line2, last2, first2, ch2);
  538. X        }
  539. X    }
  540. X    for (pline = line_root; pline; pline = pline->link) {
  541. X        fprintf(fp_active2w, fmt_active2,
  542. X            pline->name, pline->last, pline->first, pline->type);
  543. X    }
  544. X    if (!eof_active2) {
  545. X        while (fgets(line2, sizeof line2, fp_active2)) {
  546. X        fputs(line2, fp_active2w);
  547. X        }
  548. X    }
  549. X    log_stats();
  550. X    }
  551. X    wrap_it_up(ret);
  552. X}
  553. X
  554. void
  555. wrap_it_up(ret)
  556. int ret;
  557. X{
  558. X    mt_unlock(locked);
  559. X    exit(ret);
  560. X}
  561. X
  562. X/* Process the active file, creating/modifying the active2 file and
  563. X** creating/modifying the thread data files.
  564. X*/
  565. bool
  566. makethreads()
  567. X{
  568. X    register char *cp, *cp2;
  569. X    char data_file_open;
  570. X    bool update_successful, old_groups, touch_thread;
  571. X#ifdef SERVER
  572. X    int server_failure = 0;
  573. X#endif
  574. X
  575. X    /* See if an mthreads pass is already going. */
  576. X    if (!(locked & PASS_LOCK) && mt_lock(PASS_LOCK, 0) != 0) {
  577. X    log_entry("unable to get a lock for this pass.\n");
  578. X    return FALSE;
  579. X    }
  580. X#ifdef SERVER
  581. X    if (!open_server()) {
  582. X    return FALSE;
  583. X    }
  584. X    put_server("LIST");    /* ask server for the active file */
  585. X    get_server(line, sizeof line);
  586. X    if (*line != CHAR_OK) {
  587. X    log_entry("Unable to get active file from server.\n");
  588. X    close_server();
  589. X    return FALSE;
  590. X    }
  591. X    if ((fp_active = fopen(file_exp(ACTIVE1), "w+")) == Nullfp) {
  592. X    log_entry("Unable to write the active1 file -- quitting.\n");
  593. X    wrap_it_up(1);
  594. X    }
  595. X    while (1) {
  596. X    if (get_server(line, sizeof line) < 0) {
  597. X        log_entry("Server failed to send entire active file.\n");
  598. X        fclose(fp_active);
  599. X        close_server();
  600. X        return FALSE;
  601. X    }
  602. X    if (*line == '.') {
  603. X        break;
  604. X    }
  605. X    fputs(line, fp_active);
  606. X    putc('\n', fp_active);
  607. X    }
  608. X    if (ferror(fp_active)) {
  609. X    log_entry("Error writing to active1 file.\n");
  610. X    fclose(fp_active);
  611. X    close_server();
  612. X    return FALSE;
  613. X    }
  614. X    fseek(fp_active, 0L, 0);        /* rewind for read */
  615. X#else /* not SERVER */
  616. X    if ((fp_active = fopen(file_exp(ACTIVE), "r")) == Nullfp) {
  617. X    log_entry("Unable to open the active file.\n");
  618. X    wrap_it_up(1);
  619. X    }
  620. X#endif
  621. X    filename = file_exp(ACTIVE2);
  622. X    if ((fp_active2w = fopen(filename, "r+")) == Nullfp) {
  623. X    if ((fp_active2w = fopen(filename, "w")) == Nullfp) {
  624. X        log_entry("Unable to open the active2 file for update.\n");
  625. X        wrap_it_up(1);
  626. X    }
  627. X    /* Add existing groups to active.times file with ancient date. */
  628. X    old_groups = TRUE;
  629. X    } else {
  630. X    old_groups = FALSE;
  631. X    }
  632. X    if ((fp_active2 = fopen(filename, "r")) == Nullfp) {
  633. X    log_entry("Unable to open the active2 file.\n");
  634. X    wrap_it_up(1);
  635. X    }
  636. X    if (extra_expire && log_verbosity) {
  637. X    log_entry("Using enhanced expiration for this pass.\n");
  638. X    }
  639. X
  640. X    /* What time is it? */
  641. X#ifdef TZSET
  642. X    (void) time(&tnow);
  643. X    (void) tzset();
  644. X#else
  645. X    (void) ftime(&ftnow);
  646. X#endif
  647. X
  648. X    eof_active = eof_active2 = FALSE;
  649. X    fp_tmp = Nullfp;
  650. X
  651. X    /* Loop through entire active file. */
  652. X    for (;;) {
  653. X    if (eof_active || !fgets(line, sizeof line, fp_active)) {
  654. X        if (eof_active2 && !line_root) {
  655. X        break;
  656. X        }
  657. X        eof_active = TRUE;
  658. X        ch = 'x';
  659. X    } else {
  660. X        cp = line + strlen(line) - 1;
  661. X        if (*cp == '\n') {
  662. X        *cp = '\0';
  663. X        }
  664. X        if (!(cp = index(line, ' '))) {
  665. X        log_entry("** line in 'active' has no space: %s **\n", line);
  666. X        continue;
  667. X        }
  668. X        *cp = '\0';
  669. X        if (sscanf(cp+1, "%ld %ld %c", &last, &first, &ch) != 3) {
  670. X        log_entry("** digits corrupted in 'active': %s %s **\n",
  671. X            line, cp+1);
  672. X        continue;
  673. X        }
  674. X        if (last < first - 1) {
  675. X        log_entry("** bogus group values in 'active': %s %s **\n",
  676. X            line, cp+1);
  677. X        continue;
  678. X        }
  679. X    }
  680. X    if (debug > 1 || log_verbosity > 3) {
  681. X        log_entry("Processing %s:\n", line);
  682. X    }
  683. X    data_file_open = 0;
  684. X    /* If we've allocated some lines in memory while searching for
  685. X    ** newsgroups (they've scrambled the active file on us), check
  686. X    ** them first.
  687. X    */
  688. X    last_line = Nullact;
  689. X    for (pline = line_root; pline; pline = pline->link) {
  690. X        if (eof_active || strEQ(line, pline->name)) {
  691. X        strcpy(line2, pline->name);
  692. X        free(pline->name);
  693. X        first2 = pline->first;
  694. X        last2 = pline->last;
  695. X        ch2 = pline->type;
  696. X        if (last_line) {
  697. X            last_line->link = pline->link;
  698. X        } else {
  699. X            line_root = pline->link;
  700. X        }
  701. X        free(pline);
  702. X        break;
  703. X        }
  704. X        last_line = pline;
  705. X    }/* for */
  706. X    touch_thread = FALSE;
  707. X
  708. X    /* If not found yet, check the active2 file. */
  709. X    if (!pline) {
  710. X        for (;;) {
  711. X        if (eof_active2 || !fgets(line2, sizeof line2, fp_active2)) {
  712. X            /* At end of file, check if the thread data file exists.
  713. X            ** If so, use its high/low values.  Else, default to
  714. X            ** some initial values.
  715. X            */
  716. X            eof_active2 = TRUE;
  717. X            if (eof_active) {
  718. X            break;
  719. X            }
  720. X            strcpy(line2, line);
  721. X            if ((data_file_open = init_data(thread_name(line)))) {
  722. X            last2 = total.last;
  723. X            first2 = total.first;
  724. X            ch2 = 'y';
  725. X            } else {
  726. X            total.first = first2 = first;
  727. X            if (add_new && (!hierarchy_list
  728. X              || ngmatch(hierarchy_list, line) == NG_MATCH)) {
  729. X                total.last = last2 = first - 1;
  730. X                ch2 = (ch == '=' ? 'x' : ch);
  731. X                touch_thread = TRUE;
  732. X                added_groups += (ch2 != 'x');
  733. X            } else {
  734. X                total.last = last2 = last;
  735. X                ch2 = (ch == '=' ? 'X' : toupper(ch));
  736. X            }
  737. X            }
  738. X            data_file_open++;        /* (1 == empty, 2 == open) */
  739. X#ifdef ACTIVE_TIMES
  740. X            /* Found a new group -- see if we need to log it. */
  741. X            if (acttimes_flag) {
  742. X            if (!fp_tmp && !(fp_tmp = fopen(ACTIVE_TIMES, "a"))) {
  743. X                log_entry("unable to append to %s.\n",ACTIVE_TIMES);
  744. X                acttimes_flag = FALSE;
  745. X            } else {
  746. X                fprintf(fp_tmp, "%s %ld mthreads@%s\n", line,
  747. X                old_groups ? 30010440L : time(Null(time_t)),
  748. X                OURDOMAIN);
  749. X            }
  750. X            }
  751. X#endif
  752. X            break;
  753. X        }
  754. X        if (!(cp2 = index(line2, ' '))) {
  755. X            log_entry("active2 line has no space: %s\n", line2);
  756. X            continue;
  757. X        }
  758. X        *cp2 = '\0';
  759. X        if (sscanf(cp2+1,"%ld %ld %c",&last2,&first2,&ch2) != 3) {
  760. X            log_entry("active2 digits corrupted: %s %s\n",
  761. X            line2, cp2+1);
  762. X            continue;
  763. X        }
  764. X        /* Check if we're still in-sync */
  765. X        if (eof_active || strEQ(line, line2)) {
  766. X            break;
  767. X        }
  768. X        /* Nope, we've got to go looking for this line somewhere
  769. X        ** down in the file.  Save each non-matching line in memory
  770. X        ** as we go.
  771. X        */
  772. X        pline = (ACTIVE_LINE*)safemalloc(sizeof (ACTIVE_LINE));
  773. X        pline->name = savestr(line2);
  774. X        pline->last = last2;
  775. X        pline->first = first2;
  776. X        pline->type = ch2;
  777. X        pline->link = Nullact;
  778. X        if (!last_line) {
  779. X            line_root = pline;
  780. X        } else {
  781. X            last_line->link = pline;
  782. X        }
  783. X        *line2 = '\0';
  784. X        last_line = pline;
  785. X        }/* for */
  786. X        if (eof_active && eof_active2) {
  787. X        break;
  788. X        }
  789. X    }/* if !pline */
  790. X    if (eof_active) {
  791. X        strcpy(line, line2);
  792. X        if (truncate_len < 0) {
  793. X        truncate_len = ftell(fp_active2w);
  794. X        }
  795. X    }
  796. X    if (rebuild) {
  797. X        unlink(thread_name(line));
  798. X    }
  799. X    update_successful = FALSE;
  800. X    if (hierarchy_list && !add_new) {
  801. X        switch ((action = ngmatch(hierarchy_list, line))) {
  802. X        case NG_MATCH:            /* if unthreaded, add it */
  803. X        if (ch2 < 'a' && ch != 'x' && ch != '=') {
  804. X            total.last = last2 = first2 - 1;
  805. X            touch_thread = TRUE;
  806. X            added_groups++;
  807. X        }
  808. X        break;
  809. X        case NG_SKIP:            /* if threaded, remove it */
  810. X        if (ch2 >= 'a') {
  811. X            unlink(thread_name(line));
  812. X            removed_groups++;
  813. X        }
  814. X        break;
  815. X        }
  816. X    } else {
  817. X        action = (ch2 < 'a' ? NG_SKIP : NG_MATCH);
  818. X    }
  819. X    if (action == NG_DEFAULT || (debug && action == NG_SKIP)) {
  820. X        dont_read_data(data_file_open);    /* skip silently */
  821. X        if (touch_thread) {
  822. X        (void) write_data(thread_name(line));
  823. X        }
  824. X    } else if (ch == 'x' || ch == '=') {
  825. X        if (!daemon_delay) {        /* skip 'x'ed groups */
  826. X        putchar('x');
  827. X        }
  828. X        ch = (action == NG_SKIP ? 'X' : 'x');
  829. X        if ((ch2 >= 'a' && ch2 != 'x') || force_flag) {
  830. X        /* Remove thread file if group is newly 'x'ed out */
  831. X        unlink(thread_name(line));
  832. X        }
  833. X        update_successful = TRUE;
  834. X        dont_read_data(data_file_open);
  835. X    } else if (action == NG_SKIP) {    /* skip excluded groups */
  836. X        if (!daemon_delay) {
  837. X        putchar('X');
  838. X        }
  839. X        ch = toupper(ch);
  840. X        if (force_flag) {
  841. X        unlink(thread_name(line));
  842. X        }
  843. X        update_successful = TRUE;
  844. X        dont_read_data(data_file_open);
  845. X    } else if (no_processing) {
  846. X        if (!daemon_delay) {
  847. X        putchar(',');
  848. X        }
  849. X        ch2 = ch;
  850. X        dont_read_data(data_file_open);
  851. X        if (touch_thread) {
  852. X        (void) write_data(thread_name(line));
  853. X        }
  854. X    } else if (!force_flag && !extra_expire && !rebuild
  855. X     && first == first2 && last == last2) {
  856. X        /* We're up-to-date here.  Skip it. */
  857. X        if (!daemon_delay) {
  858. X        putchar('.');
  859. X        }
  860. X        update_successful = TRUE;
  861. X        dont_read_data(data_file_open);
  862. X        if (touch_thread) {
  863. X        (void) write_data(thread_name(line));
  864. X        }
  865. X    } else {
  866. X        /* Looks like we need to process something. */
  867. X#ifdef SERVER
  868. X        if (server_failure == 1) {
  869. X        if (!open_server()) {
  870. X            log_entry("failed to re-open server for this pass.\n");
  871. X            server_failure = 2;
  872. X        } else {
  873. X            server_failure = 0;
  874. X        }
  875. X        }
  876. X        if (!server_failure) {
  877. X        sprintf(buf, "GROUP %s", line);
  878. X        put_server(buf);        /* go to next group */
  879. X        if (get_server(buf, sizeof buf) < 0
  880. X         || *buf != CHAR_OK) {
  881. X            log_error("NNTP failure -- %s.\n", buf);
  882. X            if (strnNE(buf, "400", 3)) {
  883. X            close_server();
  884. X            }
  885. X            server_failure = 1;
  886. X        }
  887. X        }
  888. X        if (server_failure) {
  889. X#else
  890. X        strcpy(cp = buf, line2);
  891. X        while ((cp = index(cp, '.'))) {
  892. X        *cp = '/';
  893. X        }
  894. X        filename = file_exp(buf);        /* relative to spool dir */
  895. X        if (chdir(filename) < 0) {
  896. X        if (errno != ENOENT) {
  897. X            log_entry("Unable to chdir to `%s'.\n", filename);
  898. X        }
  899. X#endif
  900. X        if (!daemon_delay) {
  901. X            putchar('*');
  902. X        }
  903. X        dont_read_data(data_file_open);
  904. X        } else {
  905. X        filename = thread_name(line);
  906. X        /* Try to open the data file only if we didn't try it
  907. X        ** in the name matching code above.
  908. X        */
  909. X        if (!data_file_open--) {    /* (0 == haven't tried yet) */
  910. X            if (!(data_file_open = init_data(filename))) {
  911. X            total.last = first - 1;
  912. X            total.first = first;
  913. X            }
  914. X        }
  915. X
  916. X        if (data_file_open) {        /* (0 == empty, 1 == open) */
  917. X            if (!read_data()) {    /* did read fail? */
  918. X            if (debug) {
  919. X                strcpy(buf, filename);
  920. X                cp = rindex(buf, '/') + 1;
  921. X                strcpy(cp, "bad.read");
  922. X                rename(filename, buf);
  923. X            }
  924. X            data_file_open = init_data(Nullch);
  925. X            total.last = first - 1;
  926. X            total.first = first;
  927. X            }
  928. X        }
  929. X        grevious_error = FALSE;
  930. X        process_articles(first, last);
  931. X        processed_groups++;
  932. X        if (!added_count && !expired_count && !touch_thread
  933. X         && last == last2) {
  934. X            (void) write_data(Nullch);
  935. X            if (!daemon_delay) {
  936. X            putchar(':');
  937. X            }
  938. X            update_successful = TRUE;
  939. X        } else {
  940. X            strcpy(buf, filename);
  941. X            cp = rindex(buf, '/') + 1;
  942. X            strcpy(cp, NEW_THREAD);    /* write data as .new */
  943. X            if (write_data(buf) && !grevious_error) {
  944. X            rename(buf, filename);
  945. X            added_articles += added_count;
  946. X            expired_articles += expired_count;
  947. X            if (!daemon_delay) {
  948. X                if (!total.root) {
  949. X                putchar('-');
  950. X                } else {
  951. X                putchar('#');
  952. X                }
  953. X            }
  954. X            update_successful = TRUE;
  955. X            } else {
  956. X            if (debug) {
  957. X                cp = rindex(filename, '/') + 1;
  958. X                strcpy(cp, "bad.write");
  959. X                rename(buf, filename);
  960. X            } else {
  961. X                unlink(buf);    /* blow away bad write */
  962. X                if (grevious_error) {
  963. X                unlink(filename); /* blow away the .thread, */
  964. X                (void) init_data(Nullch); /* zero totals & */
  965. X                (void) write_data(filename); /* write it null */
  966. X                }
  967. X            }
  968. X            if (!daemon_delay) {
  969. X                putchar('!');
  970. X            }
  971. X            }/* if */
  972. X        }/* if */
  973. X        }/* if */
  974. X    }/* if */
  975. X    /* Finally, update the active2 entry for this newsgroup. */
  976. X    if (!eof_active) {
  977. X        if (update_successful) {
  978. X        fprintf(fp_active2w, fmt_active2, line, last, first, ch);
  979. X        } else {
  980. X        fprintf(fp_active2w, fmt_active2, line, last2, first2, ch2);
  981. X        }
  982. X    }
  983. X    *line2 = '\0';
  984. X    /* If we're not out of sync, keep active2 file flushed. */
  985. X    if (!line_root) {
  986. X        fflush(fp_active2w);
  987. X    }
  988. X#ifdef CHECKLOAD
  989. X    checkload();
  990. X#endif
  991. X    }/* for */
  992. X
  993. X#ifdef SERVER
  994. X    if (!server_failure) {
  995. X    close_server();
  996. X    }
  997. X#endif
  998. X    fclose(fp_active);
  999. X    fclose(fp_active2);
  1000. X
  1001. X    if (truncate_len >= 0) {
  1002. X#ifdef FTRUNCATE
  1003. X    if (ftruncate(fileno(fp_active2w), truncate_len) == -1)
  1004. X#else
  1005. X#ifdef MVTRUNC
  1006. X    if (mvtrunc(file_exp(ACTIVE2), truncate_len) == -1)
  1007. X#else
  1008. X    if (chsize(fileno(fp_active2w), truncate_len) == -1)
  1009. X#endif
  1010. X#endif
  1011. X    {
  1012. X        log_entry("Unable to truncate the active2 file.\n");
  1013. X    }
  1014. X    truncate_len = -1;
  1015. X    }
  1016. X    fclose(fp_active2w);
  1017. X    fp_active2w = Nullfp;
  1018. X
  1019. X    if (fp_tmp) {
  1020. X    fclose(fp_tmp);
  1021. X    }
  1022. X    log_stats();
  1023. X    processed_groups = added_groups = removed_groups = 0;
  1024. X    added_articles = expired_articles = 0;
  1025. X
  1026. X    extra_expire = FALSE;
  1027. X    rebuild = FALSE;
  1028. X
  1029. X    mt_unlock(PASS_LOCK);        /* remove single-pass lock */
  1030. X
  1031. X    return TRUE;
  1032. X}
  1033. X
  1034. X#ifdef SERVER
  1035. int
  1036. open_server()
  1037. X{
  1038. X    switch (server_init(server)) {
  1039. X    case OK_NOPOST:
  1040. X    case OK_CANPOST:
  1041. X    return 1;
  1042. X    case ERR_ACCESS:
  1043. X    log_entry("Server %s rejected connection -- quitting.\n", server);
  1044. X    wrap_it_up(1);
  1045. X    default:
  1046. X    log_entry("Couldn't connect with server %s.\n", server);
  1047. X    return 0;
  1048. X    }
  1049. X}
  1050. X#endif
  1051. X
  1052. X/*
  1053. X** ngmatch - newsgroup name matching
  1054. X**
  1055. X** returns NG_MATCH for a positive patch, NG_SKIP for a negative match,
  1056. X** and NG_DEFAULT if the group doesn't match at all.
  1057. X**
  1058. X** "all" in a pattern is a wildcard that matches exactly one word;
  1059. X** it does not cross "." (NGDELIM) delimiters.
  1060. X**
  1061. X** This matching code was borrowed from C news.
  1062. X*/
  1063. X
  1064. X#define ALL "all"            /* word wildcard */
  1065. X
  1066. X#define NGNEG '!'
  1067. X#define NGSEP ','
  1068. X#define NGDELIM '.'
  1069. X
  1070. int
  1071. ngmatch(ngpat, grp)
  1072. char *ngpat, *grp;
  1073. X{
  1074. X    register char *patp;        /* point at current pattern */
  1075. X    register char *patcomma;
  1076. X    register int depth;
  1077. X    register int faildeepest = 0, hitdeepest = 0;    /* in case no match */
  1078. X    register bool negation;
  1079. X
  1080. X    for (patp = ngpat; patp != Nullch; patp = patcomma) {
  1081. X    negation = FALSE;
  1082. X    patcomma = index(patp, NGSEP);
  1083. X    if (patcomma != Nullch) {
  1084. X        *patcomma = '\0';        /* will be restored below */
  1085. X    }
  1086. X    if (*patp == NGNEG) {
  1087. X        ++patp;
  1088. X        negation = TRUE;
  1089. X    }
  1090. X    depth = onepatmatch(patp, grp); /* try 1 pattern, 1 group */
  1091. X    if (patcomma != Nullch) {
  1092. X        *patcomma++ = NGSEP;    /* point after the comma */
  1093. X    }
  1094. X    if (depth == 0) {        /* mis-match */
  1095. X        ;                /* ignore it */
  1096. X    } else if (negation) {
  1097. X        /* record depth of deepest negated matched word */
  1098. X        if (depth > faildeepest) {
  1099. X        faildeepest = depth;
  1100. X        }
  1101. X    } else {
  1102. X        /* record depth of deepest plain matched word */
  1103. X        if (depth > hitdeepest) {
  1104. X        hitdeepest = depth;
  1105. X        }
  1106. X    }
  1107. X    }
  1108. X    if (hitdeepest > faildeepest) {
  1109. X    return NG_MATCH;
  1110. X    } else if (faildeepest) {
  1111. X    return NG_SKIP;
  1112. X    } else {
  1113. X    return NG_DEFAULT;
  1114. X    }
  1115. X}
  1116. X
  1117. X/*
  1118. X** Match a pattern against a group by looking at each word of pattern in turn.
  1119. X**
  1120. X** On a match, return the depth (roughly, ordinal number * k) of the rightmost
  1121. X** word that matches.  If group runs out first, the match fails; if pattern
  1122. X** runs out first, it succeeds.  On a failure, return zero.
  1123. X*/
  1124. int
  1125. onepatmatch(patp, grp)
  1126. char *patp, *grp;
  1127. X{
  1128. X    register char *rpatwd;        /* used by word match (inner loop) */
  1129. X    register char *patdot, *grdot;    /* point at dots after words */
  1130. X    register char *patwd, *grwd;    /* point at current words */
  1131. X    register int depth = 0;
  1132. X
  1133. X    for (patwd = patp, grwd = grp;
  1134. X     patwd != Nullch && grwd != Nullch;
  1135. X     patwd = patdot, grwd = grdot
  1136. X    ) {
  1137. X    register bool match = FALSE;
  1138. X    register int incr = 20;
  1139. X
  1140. X    /* null-terminate words */
  1141. X    patdot = index(patwd, NGDELIM);
  1142. X    if (patdot != Nullch) {
  1143. X        *patdot = '\0';        /* will be restored below */
  1144. X    }
  1145. X    grdot = index(grwd, NGDELIM);
  1146. X    if (grdot != Nullch) {
  1147. X        *grdot = '\0';        /* will be restored below */
  1148. X    }
  1149. X    /*
  1150. X     * Match one word of pattern with one word of group.
  1151. X     * A pattern word of "all" matches any group word,
  1152. X     * but isn't worth as much.
  1153. X     */
  1154. X#ifdef FAST_STRCMP
  1155. X    match = STREQ(patwd, grwd);
  1156. X    if (!match && STREQ(patwd, ALL)) {
  1157. X        match = TRUE;
  1158. X        --incr;
  1159. X    }
  1160. X#else
  1161. X    for (rpatwd = patwd; *rpatwd == *grwd++;) {
  1162. X        if (*rpatwd++ == '\0') {
  1163. X        match = TRUE;        /* literal match */
  1164. X        break;
  1165. X        }
  1166. X    }
  1167. X    if (!match) {
  1168. X        /* ugly special case match for "all" */
  1169. X        rpatwd = patwd;
  1170. X        if (*rpatwd++ == 'a' && *rpatwd++ == 'l'
  1171. X         && *rpatwd++ == 'l' && *rpatwd   == '\0') {
  1172. X        match = TRUE;
  1173. X         --incr;
  1174. X        }
  1175. X    }
  1176. X#endif                /* FAST_STRCMP */
  1177. X
  1178. X    if (patdot != Nullch) {
  1179. X        *patdot++ = NGDELIM;    /* point after the dot */
  1180. X    }
  1181. X    if (grdot != Nullch) {
  1182. X        *grdot++ = NGDELIM;
  1183. X    }
  1184. X    if (!match) {
  1185. X        depth = 0;        /* words differed - mismatch */
  1186. X        break;
  1187. X    }
  1188. X    depth += incr;
  1189. X    }
  1190. X    /* if group name ran out before pattern, then match fails */
  1191. X    if (grwd == Nullch && patwd != Nullch) {
  1192. X    depth = 0;
  1193. X    }
  1194. X    return depth;
  1195. X}
  1196. X
  1197. X/* Put our startup options into the log file.
  1198. X*/
  1199. void
  1200. log_startup()
  1201. X{
  1202. X    char tmpbuf[256];
  1203. X
  1204. X    strcpy(tmpbuf, "Started mthreads");
  1205. X    if (cron_locking) {
  1206. X    strcat(tmpbuf, " -c");
  1207. X    }
  1208. X    if (debug) {
  1209. X    strcat(tmpbuf, " -D");
  1210. X    }
  1211. X    if (force_flag) {
  1212. X    strcat(tmpbuf, " -f");
  1213. X    }
  1214. X    if (daemon_delay) {
  1215. X    sprintf(tmpbuf + strlen(tmpbuf), " -d%d", daemon_delay / 60);
  1216. X    if (expire_time) {
  1217. X        struct tm *ts;
  1218. X
  1219. X        ts = localtime(&expire_time);
  1220. X        sprintf(tmpbuf + strlen(tmpbuf), " -e%02d%02d",ts->tm_hour,ts->tm_min);
  1221. X    }
  1222. X    } else if (expire_time) {
  1223. X    strcat(tmpbuf, " -e");
  1224. X    }
  1225. X    if (slow_down) {
  1226. X    sprintf(tmpbuf + strlen(tmpbuf), " -s%d", slow_down);
  1227. X    }
  1228. X    if (no_processing) {
  1229. X    strcat(tmpbuf, " -n");
  1230. X    }
  1231. X    if (log_verbosity) {
  1232. X    sprintf(tmpbuf + strlen(tmpbuf), " -v%d", log_verbosity);
  1233. X    }
  1234. X    if (zap_thread) {
  1235. X    strcat(tmpbuf, " -z");
  1236. X    }
  1237. X    if (add_new) {
  1238. X    if (hierarchy_list) {
  1239. X        sprintf(tmpbuf + strlen(tmpbuf), " -a %s", hierarchy_list);
  1240. X    } else {
  1241. X        strcat(tmpbuf, " -a all");
  1242. X    }
  1243. X    } else if (hierarchy_list) {
  1244. X    sprintf(tmpbuf + strlen(tmpbuf), " %s (only)", hierarchy_list);
  1245. X    }
  1246. X    log_entry("%s\n", tmpbuf);
  1247. X}
  1248. X
  1249. X/* Put our statistics into the log file.
  1250. X*/
  1251. void
  1252. log_stats()
  1253. X{
  1254. X    sprintf(line, "Processed %d group%s:  added %d article%s, expired %d.\n",
  1255. X    processed_groups, processed_groups == 1 ? nullstr : "s",
  1256. X    added_articles, added_articles == 1 ? nullstr : "s",
  1257. X    expired_articles);
  1258. X
  1259. X    log_entry(line);
  1260. X
  1261. X    if (!daemon_delay) {
  1262. X    putchar('\n');
  1263. X    fputs(line, stdout);
  1264. X    }
  1265. X    if (added_groups) {
  1266. X    sprintf(line, "Turned %d group%s on.\n", added_groups,
  1267. X        added_groups == 1 ? nullstr : "s");
  1268. X    log_entry(line);
  1269. X    if (!daemon_delay) {
  1270. X        fputs(line, stdout);
  1271. X    }
  1272. X    }
  1273. X    if (removed_groups) {
  1274. X    sprintf(line, "Turned %d group%s off.\n", removed_groups,
  1275. X        removed_groups == 1 ? nullstr : "s");
  1276. X    log_entry(line);
  1277. X    if (!daemon_delay) {
  1278. X        fputs(line, stdout);
  1279. X    }
  1280. X    }
  1281. X}
  1282. X/* Generate a log entry with timestamp.
  1283. X*/
  1284. X/*VARARGS1*/
  1285. void
  1286. log_entry(fmt, arg1, arg2, arg3)
  1287. char *fmt;
  1288. long arg1, arg2, arg3;
  1289. X{
  1290. X#ifndef USESYSLOG
  1291. X    time_t now;
  1292. X    char *ctime();
  1293. X#endif
  1294. X
  1295. X    if (initializing) {
  1296. X    fprintf(stderr, fmt, arg1, arg2, arg3);
  1297. X    return;
  1298. X    }
  1299. X
  1300. X#ifndef USESYSLOG
  1301. X    (void) time(&now);
  1302. X    fprintf(fp_log, "%.12s%c", ctime(&now)+4, daemon_delay ? ' ' : '+');
  1303. X    fprintf(fp_log, fmt, arg1, arg2, arg3);
  1304. X    fflush(fp_log);
  1305. X#else
  1306. X    syslog(LOG_INFO, fmt, arg1, arg2, arg3);
  1307. X#endif
  1308. X}
  1309. X
  1310. X/* Generate a log entry, with 'E'rror flagging (non-daemon mode), time-stamp,
  1311. X** and newsgroup name.
  1312. X*/
  1313. X/*VARARGS1*/
  1314. void
  1315. log_error(fmt, arg1, arg2, arg3)
  1316. char *fmt;
  1317. long arg1;
  1318. long arg2;
  1319. long arg3;
  1320. X{
  1321. X    char fmtbuf[256];
  1322. X
  1323. X    sprintf(fmtbuf, "%s: %s", line, fmt);
  1324. X#ifndef USESYSLOG
  1325. X    log_entry(fmtbuf, arg1, arg2, arg3);
  1326. X#else
  1327. X    syslog(LOG_NOTICE, fmtbuf, arg1, arg2, arg3);
  1328. X#endif
  1329. X    if (*fmt == '*') {
  1330. X    grevious_error = TRUE;
  1331. X    if (!daemon_delay) {
  1332. X        putchar('E');
  1333. X    }
  1334. X    }
  1335. X    else {
  1336. X    if (!daemon_delay) {
  1337. X        putchar('e');
  1338. X    }
  1339. X    }
  1340. X}
  1341. X
  1342. X#ifdef MYCHSIZE
  1343. X    /* code courtesy of William Kucharski */
  1344. X
  1345. int
  1346. chsize(fd, length)
  1347. int fd;            /* file descriptor */
  1348. off_t length;        /* length to set file to */
  1349. X{
  1350. X    extern long lseek();
  1351. X    struct flock fl;
  1352. X
  1353. X    if (fstat(fd, &filestat) < 0) {
  1354. X    return -1;
  1355. X    }
  1356. X    if (filestat.st_size < length) {    /* extend file length */
  1357. X    /* Write a 0 byte at the end. */
  1358. X    if (lseek(fd, length - 1, 0) < 0
  1359. X     || write(fd, "", 1) != 1) {
  1360. X        return -1;
  1361. X    }
  1362. X    } else {
  1363. X    /* Truncate file at length. */
  1364. X    fl.l_whence = 0;
  1365. X    fl.l_len = 0;
  1366. X    fl.l_start = length;
  1367. X    fl.l_type = F_WRLCK;        /* write lock on file space */
  1368. X
  1369. X    /*
  1370. X    ** This relies on the UNDOCUMENTED F_FREESP argument to
  1371. X    ** fcntl(2), which truncates the file so that it ends at the
  1372. X    ** position indicated by fl.l_start.
  1373. X    **
  1374. X    ** Will minor miracles never cease?
  1375. X    */
  1376. X    if (fcntl(fd, F_FREESP, &fl) < 0) {
  1377. X        return -1;
  1378. X    }
  1379. X    }
  1380. X    return 0;
  1381. X}
  1382. X#endif
  1383. X
  1384. X#ifdef MVTRUNC
  1385. int
  1386. mvtrunc(filename, truncate_len)
  1387. char *filename;
  1388. long truncate_len;
  1389. X{
  1390. X    FILE *fp_in, *fp_out;
  1391. X
  1392. X    sprintf(line, "%s.new", filename);
  1393. X    if ((fp_out = fopen(line, "w")) == Nullfp) {
  1394. X    log_entry("Tried to create active2.new.\n");
  1395. X    return -1;
  1396. X    }
  1397. X    if ((fp_in = fopen(filename, "r")) == Nullfp) {
  1398. X    fclose(fp_out);
  1399. X    unlink(line);
  1400. X    log_entry("Tried to re-open the active2 file.\n");
  1401. X    return -1;
  1402. X    }
  1403. X    while (ftell(fp_out) < truncate_len) {
  1404. X    if (!fgets(buf, sizeof buf, fp_in)) {
  1405. X        break;
  1406. X    }
  1407. X    fputs(buf, fp_out);
  1408. X    }
  1409. X    sprintf(buf, "%s.old", filename);
  1410. X    rename(filename, buf);
  1411. X    rename(line, filename);
  1412. X    fclose(fp_in);
  1413. X    fclose(fp_out);
  1414. X
  1415. X    return 0;
  1416. X}
  1417. X#endif
  1418. X
  1419. X#ifndef RENAME
  1420. int
  1421. rename(old, new)
  1422. char    *old, *new;
  1423. X{
  1424. X    struct stat st;
  1425. X
  1426. X    if (stat(old, &st) == -1) {
  1427. X    return -1;
  1428. X    }
  1429. X    if (unlink(new) == -1 && errno != ENOENT) {
  1430. X    return -1;
  1431. X    }
  1432. X    if (link(old, new) == -1) {
  1433. X    return -1;
  1434. X    }
  1435. X    if (unlink(old) == -1) {
  1436. X    int e = errno;
  1437. X    (void) unlink(new);
  1438. X    errno = e;
  1439. X    return -1;
  1440. X    }
  1441. X    return 0;
  1442. X}
  1443. X#endif /*RENAME*/
  1444. X
  1445. X#ifdef CHECKLOAD
  1446. X
  1447. X#define FREQCHECK    300
  1448. X#define SLPLOAD        300
  1449. X#define UPTIME        "/usr/ucb/uptime"
  1450. X#define TOOHIGH        5
  1451. X
  1452. checkload()
  1453. X{
  1454. X    static long lastcheck = 0;
  1455. X    long time(), i;
  1456. X    FILE *pp;
  1457. X    char buf[BUFSIZ];
  1458. X    register char *cp;
  1459. X    char *strrchr();
  1460. X
  1461. X    i = time(Null(time_t*));
  1462. X    if ((i - lastcheck) < FREQCHECK) {
  1463. X    return;
  1464. X    }
  1465. X    lastcheck = i;
  1466. X
  1467. again:
  1468. X    if ((pp = popen(UPTIME, "r")) == NULL) {
  1469. X    return;
  1470. X    }
  1471. X    if (fgets(buf, BUFSIZ, pp) == NULL) {
  1472. X    pclose(pp);
  1473. X    return;
  1474. X    }
  1475. X    pclose(pp);
  1476. X    if ((cp = strrchr(buf, ':')) == NULL) {
  1477. X    return;
  1478. X    }
  1479. X    if (atoi(cp + 2) >= TOOHIGH) {
  1480. X    sleep(SLPLOAD);
  1481. X    goto again;
  1482. X    } else {
  1483. X    return;
  1484. X    }
  1485. X}
  1486. X#endif
  1487. END_OF_FILE
  1488. if test 33893 -ne `wc -c <'mthreads.c'`; then
  1489.     echo shar: \"'mthreads.c'\" unpacked with wrong size!
  1490. fi
  1491. # end of 'mthreads.c'
  1492. fi
  1493. if test -f 'ng.c' -a "${1}" != "-c" ; then 
  1494.   echo shar: Will not clobber existing file \"'ng.c'\"
  1495. else
  1496. echo shar: Extracting \"'ng.c'\" \(35048 characters\)
  1497. sed "s/^X//" >'ng.c' <<'END_OF_FILE'
  1498. X/* $Id: ng.c,v 4.4 1991/09/09 20:23:31 sob Exp sob $
  1499. X *
  1500. X * $Log: ng.c,v $
  1501. X * Revision 4.4  1991/09/09  20:23:31  sob
  1502. X * release 4.4
  1503. X *
  1504. X *
  1505. X * 
  1506. X */
  1507. X/* This software is Copyright 1991 by Stan Barber. 
  1508. X *
  1509. X * Permission is hereby granted to copy, reproduce, redistribute or otherwise
  1510. X * use this software as long as: there is no monetary profit gained
  1511. X * specifically from the use or reproduction of this software, it is not
  1512. X * sold, rented, traded or otherwise marketed, and this copyright notice is
  1513. X * included prominently in any copy made. 
  1514. X *
  1515. X * The author make no claims as to the fitness or correctness of this software
  1516. X * for any use whatsoever, and it is provided as is. Any use of this software
  1517. X * is at the user's own risk. 
  1518. X */
  1519. X
  1520. X#include "EXTERN.h"
  1521. X#include "common.h"
  1522. X#include "rn.h"
  1523. X#include "term.h"
  1524. X#include "final.h"
  1525. X#include "util.h"
  1526. X#include "artsrch.h"
  1527. X#include "cheat.h"
  1528. X#include "help.h"
  1529. X#include "kfile.h"
  1530. X#include "rcstuff.h"
  1531. X#include "head.h"
  1532. X#include "bits.h"
  1533. X#include "art.h"
  1534. X#include "artio.h"
  1535. X#include "ngstuff.h"
  1536. X#include "intrp.h"
  1537. X#include "respond.h"
  1538. X#include "ngdata.h"
  1539. X#include "backpage.h"
  1540. X#include "rcln.h"
  1541. X#include "last.h"
  1542. X#include "search.h"
  1543. X#ifdef SERVER
  1544. X#include "server.h"
  1545. X#endif
  1546. X#ifdef USETHREADS
  1547. X#include "threads.h"
  1548. X#include "rthreads.h"
  1549. X#endif
  1550. X#include "decode.h"
  1551. X#include "INTERN.h"
  1552. X#include "ng.h"
  1553. X#include "artstate.h"            /* somebody has to do it */
  1554. X
  1555. X/* art_switch() return values */
  1556. X
  1557. X#define AS_NORM 0
  1558. X#define AS_INP 1
  1559. X#define AS_ASK 2
  1560. X#define AS_CLEAN 3
  1561. X
  1562. ART_NUM recent_art = -1;    /* previous article # for '-' command */
  1563. ART_NUM curr_art = -1;        /* current article # */
  1564. int exit_code = NG_NORM;
  1565. X
  1566. void
  1567. ng_init()
  1568. X{
  1569. X
  1570. X#ifdef KILLFILES
  1571. X    open_kfile(KF_GLOBAL);
  1572. X#endif
  1573. X#ifdef CUSTOMLINES
  1574. X    init_compex(&hide_compex);
  1575. X    init_compex(&page_compex);
  1576. X#endif
  1577. X}
  1578. X
  1579. X/* do newsgroup on line ng with name ngname */
  1580. X
  1581. X/* assumes that we are chdir'ed to SPOOL, and assures that that is
  1582. X * still true upon return, but chdirs to SPOOL/ngname in between
  1583. X *
  1584. X * If you can understand this routine, you understand most of the program.
  1585. X * The basic structure is:
  1586. X *    for each desired article
  1587. X *        for each desired page
  1588. X *            for each line on page
  1589. X *                if we need another line from file
  1590. X *                    get it
  1591. X *                    if it's a header line
  1592. X *                        do special things
  1593. X *                for each column on page
  1594. X *                    put out a character
  1595. X *                end loop
  1596. X *            end loop
  1597. X *        end loop
  1598. X *    end loop
  1599. X *
  1600. X *    (Actually, the pager is in another routine.)
  1601. X *
  1602. X * The chief problem is deciding what is meant by "desired".  Most of
  1603. X * the messiness of this routine is due to the fact that people want
  1604. X * to do unstructured things all the time.  I have used a few judicious
  1605. X * goto's where I thought it improved readability.  The rest of the messiness
  1606. X * arises from trying to be both space and time efficient.  Have fun.
  1607. X */
  1608. X
  1609. int
  1610. do_newsgroup(start_command)
  1611. char *start_command;            /* command to fake up first */
  1612. X{
  1613. X#ifdef SERVER
  1614. X    char artname[MAXFILENAME];
  1615. X    char intrpwork[MAXFILENAME];
  1616. X    static long our_pid=0;
  1617. X#endif /* SERVER */
  1618. X    char oldmode = mode;
  1619. X    register long i;            /* scratch */
  1620. X    int skipstate;            /* how many unavailable articles */
  1621. X                    /*   have we skipped already? */
  1622. X    
  1623. X    char *whatnext = "%sWhat next? [%s]";
  1624. X
  1625. X#ifdef SERVER
  1626. X    if (our_pid == 0)           /* Agreed, this is gross */
  1627. X        our_pid = getpid();
  1628. X#endif /* SERVER */
  1629. X
  1630. X#ifdef ARTSEARCH
  1631. X    srchahead = (scanon && ((ART_NUM)toread[ng]) >= scanon ? -1 : 0);
  1632. X                    /* did they say -S? */
  1633. X#endif
  1634. X    
  1635. X    mode = 'a';
  1636. X#ifdef USETHREADS
  1637. X    recent_p_art = curr_p_art = Nullart;
  1638. X#endif
  1639. X    recent_art = curr_art = -1;
  1640. X    exit_code = NG_NORM;
  1641. X
  1642. X#ifdef SERVER
  1643. X    sprintf(ser_line, "GROUP %s", ngname);
  1644. X#ifdef DEBUGGING
  1645. X    if (debug & DEB_NNTP)
  1646. X    printf(">%s\n", ser_line) FLUSH;
  1647. X#endif
  1648. X    put_server(ser_line);
  1649. X    if (nntp_get(ser_line, sizeof(ser_line)) < 0) {
  1650. X    fprintf(stderr, "\nrrn: Unexpected close of server socket.\n");
  1651. X    finalize(1);
  1652. X    }
  1653. X#ifdef DEBUGGING
  1654. X    if (debug & DEB_NNTP)
  1655. X    printf("<%s\n", ser_line) FLUSH;
  1656. X#endif
  1657. X    if (*ser_line != CHAR_OK) {
  1658. X    if (atoi(ser_line) != ERR_NOGROUP){
  1659. X        fprintf(stderr, "\nrrn: server response to GROUP %s:\n%s\n",
  1660. X            ngname, ser_line);
  1661. X        finalize(1);
  1662. X    }
  1663. X    return (-1);
  1664. X    }
  1665. X#else /* not SERVER */
  1666. X    if (eaccess(ngdir,5)) {        /* directory read protected? */
  1667. X    if (eaccess(ngdir,0)) {
  1668. X#ifdef VERBOSE
  1669. X        IF(verbose)
  1670. X        printf("\nNewsgroup %s does not have a spool directory!\n",
  1671. X            ngname) FLUSH;
  1672. X        ELSE
  1673. X#endif
  1674. X#ifdef TERSE
  1675. X        printf("\nNo spool for %s!\n",ngname) FLUSH;
  1676. X#endif
  1677. X#ifdef CATCHUP
  1678. X        catch_up(ng);
  1679. X#else
  1680. X        toread[ng] = TR_NONE;
  1681. X#endif
  1682. X    }
  1683. X    else {
  1684. X#ifdef VERBOSE
  1685. X        IF(verbose)
  1686. X        printf("\nNewsgroup %s is not currently accessible.\n",
  1687. X            ngname) FLUSH;
  1688. X        ELSE
  1689. X#endif
  1690. X#ifdef TERSE
  1691. X        printf("\n%s not readable.\n",ngname) FLUSH;
  1692. X#endif
  1693. X        toread[ng] = TR_NONE;    /* make this newsgroup invisible */
  1694. X                    /* (temporarily) */
  1695. X    }
  1696. X    mode = oldmode;
  1697. X    return -1;
  1698. X    }
  1699. X
  1700. X    /* chdir to newsgroup subdirectory */
  1701. X
  1702. X    if (chdir(ngdir)) {
  1703. X    printf(nocd,ngdir) FLUSH;
  1704. X    mode = oldmode;
  1705. X    return -1;
  1706. X    }
  1707. X#endif /* SERVER */
  1708. X
  1709. X#ifdef CACHESUBJ
  1710. X    subj_list = Null(char **);        /* no subject list till needed */
  1711. X#endif
  1712. X    
  1713. X    /* initialize control bitmap */
  1714. X
  1715. X    if (initctl()) {
  1716. X    mode = oldmode;
  1717. X    return -1;
  1718. X    }
  1719. X
  1720. X    /* FROM HERE ON, RETURN THRU CLEANUP OR WE ARE SCREWED */
  1721. X
  1722. X    in_ng = TRUE;            /* tell the world we are here */
  1723. X    forcelast = TRUE;            /* if 0 unread, do not bomb out */
  1724. X
  1725. X#ifdef USETHREADS
  1726. X    if (use_threads)        /* grab thread data */
  1727. X    ThreadedGroup = use_data(ThreadedGroup);
  1728. X#endif
  1729. X
  1730. X    /* remember what newsgroup we were in for sake of posterity */
  1731. X
  1732. X    writelast();
  1733. X
  1734. X    /* see if there are any special searches to do */
  1735. X
  1736. X#ifdef KILLFILES
  1737. X    open_kfile(KF_LOCAL);
  1738. X#ifdef VERBOSE
  1739. X    IF(verbose)
  1740. X    kill_unwanted(firstart,"Looking for articles to kill...\n\n",TRUE);
  1741. X    ELSE
  1742. X#endif
  1743. X#ifdef TERSE
  1744. X    kill_unwanted(firstart,"Killing...\n\n",TRUE);
  1745. X#endif
  1746. X#endif
  1747. X#ifdef USETHREADS
  1748. X    first_art();
  1749. X#else
  1750. X    art=firstart;
  1751. X#endif
  1752. X    
  1753. X    /* do they want a special top line? */
  1754. X
  1755. X    firstline = getval("FIRSTLINE",Nullch);
  1756. X
  1757. X    /* custom line suppression, custom page ending */
  1758. X
  1759. X#ifdef CUSTOMLINES
  1760. X    if (hideline = getval("HIDELINE",Nullch))
  1761. X    compile(&hide_compex,hideline,TRUE,TRUE);
  1762. X    if (pagestop = getval("PAGESTOP",Nullch))
  1763. X    compile(&page_compex,pagestop,TRUE,TRUE);
  1764. X#endif
  1765. X
  1766. X    /* now read each unread article */
  1767. X
  1768. X    rc_changed = doing_ng = TRUE;    /* enter the twilight zone */
  1769. X    skipstate = 0;            /* we have not skipped anything (yet) */
  1770. X    checkcount = 0;            /* do not checkpoint for a while */
  1771. X    do_fseek = FALSE;            /* start 1st article at top */
  1772. X    if (art > lastart)
  1773. X#ifdef USETHREADS
  1774. X    first_art();
  1775. X#else
  1776. X    art=firstart;            /* init the for loop below */
  1777. X#endif
  1778. X    for (; art<=lastart+1; ) {        /* for each article */
  1779. X
  1780. X    /* do we need to "grow" the newsgroup? */
  1781. X
  1782. X#ifdef USETHREADS
  1783. X    if (ThreadedGroup) {
  1784. X        if ((art > lastart || forcegrow) && getngsize(ng) > total.last) {
  1785. X        if (art > lastart)
  1786. X            art = 0;
  1787. X        unuse_data(1);        /* free data with selections saved */
  1788. X        ThreadedGroup = use_data(TRUE);    /* grows ctl & sets lastart */
  1789. X        if (!art)
  1790. X            if (!forcelast)
  1791. X            first_art();
  1792. X            else
  1793. X            art = lastart+1;
  1794. X        find_article(art);
  1795. X        curr_p_art = p_art;
  1796. X        forcegrow = FALSE;
  1797. X        }
  1798. X    }
  1799. X    else
  1800. X#endif
  1801. X    if (art > lastart || forcegrow)
  1802. X        grow_ctl(getngsize(ng));
  1803. X    check_first(art);        /* make sure firstart is still 1st */
  1804. X    if (start_command) {        /* fake up an initial command? */
  1805. X        prompt = whatnext;
  1806. X        strcpy(buf,start_command);
  1807. X        free(start_command);
  1808. X        start_command = Nullch;
  1809. X#ifdef USETHREADS
  1810. X        p_art = Nullart;
  1811. X#endif
  1812. X        art = lastart+1;
  1813. X        goto article_level;
  1814. X    }
  1815. X    if (art>lastart) {        /* are we off the end still? */
  1816. X        ART_NUM ucount = 0;        /* count of unread articles left */
  1817. X
  1818. X        for (i=firstart; i<=lastart; i++)
  1819. X        if (!(ctl_read(i)))
  1820. X            ucount++;        /* count the unread articles */
  1821. X#ifdef DEBUGGING
  1822. X        /*NOSTRICT*/
  1823. X        if (debug && ((ART_NUM)toread[ng]) != ucount)
  1824. X        printf("(toread=%ld sb %ld)",(long)toread[ng],(long)ucount)
  1825. X          FLUSH;
  1826. X#endif
  1827. X        /*NOSTRICT*/
  1828. X        toread[ng] = (ART_UNREAD)ucount;    /* this is perhaps pointless */
  1829. X        art = lastart + 1;        /* keep bitmap references sane */
  1830. X        if (art != curr_art) {
  1831. X#ifdef USETHREADS
  1832. X        recent_p_art = curr_p_art;
  1833. X        find_article(art);
  1834. X        curr_p_art = p_art;
  1835. X#endif
  1836. X        recent_art = curr_art;
  1837. X                    /* remember last article # (for '-') */
  1838. X        curr_art = art;      /* remember this article # */
  1839. X        }
  1840. X#ifdef USETHREADS
  1841. X        if (ThreadedGroup)
  1842. X        ucount -= unthreaded;
  1843. X        if (!forcelast && selected_root_cnt && !selected_count && ucount) {
  1844. X        strcpy(buf, "+");
  1845. X        prompt = whatnext;
  1846. X        goto article_level;
  1847. X        }
  1848. X#endif
  1849. X        if (erase_screen)
  1850. X        clear();            /* clear the screen */
  1851. X        else
  1852. X        fputs("\n\n",stdout) FLUSH;
  1853. X#ifdef VERBOSE
  1854. X        IF(verbose)
  1855. X        printf("End of newsgroup %s.",ngname);
  1856. X                    /* print pseudo-article */
  1857. X        ELSE
  1858. X#endif
  1859. X#ifdef TERSE
  1860. X        printf("End of %s",ngname);
  1861. X#endif
  1862. X        if (ucount) {
  1863. X#ifdef USETHREADS
  1864. X        if (selected_root_cnt)
  1865. X            printf("  (%ld + %ld articles still unread)",
  1866. X            (long)selected_count,(long)ucount-selected_count);
  1867. X        else
  1868. X#endif
  1869. X            printf("  (%ld article%s still unread)",
  1870. X            (long)ucount,ucount==1?nullstr:"s");
  1871. X        }
  1872. X        else {
  1873. X#if defined(USETHREADS) && !defined(USETMPTHREAD)
  1874. X        if (tobethreaded)
  1875. X            printf("  (%d article%s not yet threaded)",
  1876. X            tobethreaded, tobethreaded == 1 ? nullstr : "s") FLUSH;
  1877. X#endif
  1878. X        if (!forcelast)
  1879. X            goto cleanup;    /* actually exit newsgroup */
  1880. X        }
  1881. X        prompt = whatnext;
  1882. X#ifdef ARTSEARCH
  1883. X        srchahead = 0;        /* no more subject search mode */
  1884. X#endif
  1885. X        fputs("\n\n",stdout) FLUSH;
  1886. X        skipstate = 0;        /* back to none skipped */
  1887. X    }
  1888. X    else if (!reread && was_read(art)) {
  1889. X                    /* has this article been read? */
  1890. X#ifdef USETHREADS
  1891. X        follow_thread('n');
  1892. X#else
  1893. X        art++;            /* then skip it */
  1894. X#endif
  1895. X        continue;
  1896. X    }
  1897. X    else if
  1898. X      (!reread && !was_read(art)
  1899. X#ifdef SERVER
  1900. X        && nntpopen(art,GET_HEADER) == Nullfp) { 
  1901. X#else
  1902. X        && artopen(art) == Nullfp) { /* never read it, & cannot find it? */
  1903. X        if (errno != ENOENT) {    /* has it not been deleted? */
  1904. X#ifdef VERBOSE
  1905. X        IF(verbose)
  1906. X            printf("\n(Article %ld exists but is unreadable.)\n",
  1907. X            (long)art) FLUSH;
  1908. X        ELSE
  1909. X#endif /* VERBOSE */
  1910. X#ifdef TERSE
  1911. X            printf("\n(%ld unreadable.)\n",(long)art) FLUSH;
  1912. X#endif /* TERSE */
  1913. X        skipstate = 0;
  1914. X        sleep(2);
  1915. X        }
  1916. X#endif /* SERVER */
  1917. X        switch(skipstate++) {
  1918. X        case 0:
  1919. X        clear();
  1920. X#ifdef VERBOSE
  1921. X        IF(verbose)
  1922. X            fputs("Skipping unavailable article",stdout);
  1923. X        ELSE
  1924. X#endif /* VERBOSE */
  1925. X#ifdef TERSE
  1926. X            fputs("Skipping",stdout);
  1927. X#endif /* TERSE */
  1928. X        pad(just_a_sec/3);
  1929. X        sleep(1);
  1930. X        break;
  1931. X        case 1:
  1932. X        fputs("..",stdout);
  1933. X        fflush(stdout);
  1934. X        break;
  1935. X        default:
  1936. X        putchar('.');
  1937. X        fflush(stdout);
  1938. X#ifndef SERVER
  1939. X#define READDIR
  1940. X#ifdef READDIR
  1941. X        {            /* fast skip patch */
  1942. X            ART_NUM newart;
  1943. X            
  1944. X            if (! (newart=getngmin(".",art)))
  1945. X            newart = lastart+1;
  1946. X            for (i=art; i<newart; i++)
  1947. X            oneless(i);
  1948. X#ifndef USETHREADS
  1949. X            art = newart - 1;
  1950. X#endif
  1951. X        }
  1952. X#endif /* READDIR */
  1953. X#else
  1954. X        {
  1955. X            char    ser_line[NNTP_STRLEN];
  1956. X            ART_NUM    newart;
  1957. X
  1958. X            if (isfirstart) {
  1959. X                sprintf(ser_line, "STAT %d",absfirst);
  1960. X                put_server(ser_line);
  1961. X                if (nntp_get(ser_line, sizeof(ser_line)) < 0) {
  1962. X                    fprintf(stderr, 
  1963. X                    "\nrrn: Unexpected close of server socket.\n");
  1964. X                    finalize(1);
  1965. X                }
  1966. X                newart=absfirst;
  1967. X                isfirstart=FALSE;
  1968. X            } 
  1969. X            else {
  1970. X                put_server("NEXT");
  1971. X                if  (nntp_get(ser_line, sizeof(ser_line)) < 0) {
  1972. X                    fprintf(stderr,
  1973. X                    "\nrrn: unexpected close of server socket.\n");
  1974. X                    finalize(1);
  1975. X                }
  1976. X                if (ser_line[0] != CHAR_OK) {
  1977. X                    newart = lastart + 1;
  1978. X                }
  1979. X                else
  1980. X                    newart = atoi(ser_line+4);
  1981. X            }                
  1982. X            for (i=art; i<newart; i++)
  1983. X                oneless(i);
  1984. X#ifndef USETHREADS
  1985. X            art = newart - 1;
  1986. X#endif
  1987. X        }
  1988. X#endif /* SERVER */
  1989. X        break;
  1990. X        }
  1991. X        oneless(art);        /* mark deleted as read */
  1992. X#ifdef USETHREADS
  1993. X        count_roots(FALSE);        /* Keep selected_count accurate */
  1994. X        find_article(art);
  1995. X        follow_thread('n');
  1996. X#else
  1997. X        art++;            /* try next article */
  1998. X#endif
  1999. X        continue;
  2000. X    }
  2001. X    else {                /* we have a real live article */
  2002. X        skipstate = 0;        /* back to none skipped */
  2003. X        if (art != curr_art) {
  2004. X#ifdef USETHREADS
  2005. X        recent_p_art = curr_p_art;
  2006. X        find_article(art);
  2007. X        curr_p_art = p_art;
  2008. X#endif
  2009. X        recent_art = curr_art;
  2010. X                    /* remember last article # (for '-') */
  2011. X        curr_art = art;      /* remember this article # */
  2012. X        }
  2013. X        if (!do_fseek) {        /* starting at top of article? */
  2014. X        artline = 0;        /* start at the beginning */
  2015. X        topline = -1;        /* and remember top line of screen */
  2016. X                    /*  (line # within article file) */
  2017. X        }
  2018. X        clear();            /* clear screen */
  2019. X        if (!artopen(art)) {    /* make sure article is found & open */
  2020. X#ifdef USETHREADS
  2021. X        char tmpbuf[256];
  2022. X        /* see if we have tree data for this article anyway */
  2023. X        init_tree();
  2024. X        sprintf(tmpbuf,"%s #%ld is not available.",ngname,(long)art);
  2025. X        tree_puts(tmpbuf,0,0);
  2026. X        vwtary((ART_LINE)0,(ART_POS)0);
  2027. X        finish_tree(1);
  2028. X        prompt = whatnext;
  2029. X#else
  2030. X        printf("Article %ld of %s is not available.\n\n",
  2031. X            (long)art,ngname) FLUSH;
  2032. X        prompt = whatnext;
  2033. X#endif
  2034. X#ifdef ARTSEARCH
  2035. X        srchahead = 0;
  2036. X#endif
  2037. X        }
  2038. X        else {            /* found it, so print it */
  2039. X        switch (do_article()) {
  2040. X        case DA_CLEAN:        /* quit newsgroup */
  2041. X            goto cleanup;
  2042. X        case DA_TOEND:        /* do not mark as read */
  2043. X            goto reask_article; 
  2044. X        case DA_RAISE:        /* reparse command at end of art */
  2045. X            goto article_level;
  2046. X        case DA_NORM:        /* normal end of article */
  2047. X            break;
  2048. X        }
  2049. X        }
  2050. X        if (art >= absfirst)    /* don't mark non-existant articles */
  2051. X        mark_as_read();        /* mark current article as read */
  2052. X        do_hiding = TRUE;
  2053. X#ifdef ROTATION
  2054. X        rotate = FALSE;
  2055. X#endif
  2056. X    }
  2057. X
  2058. X/* if these gotos bother you, think of this as a little state machine */
  2059. X
  2060. reask_article:
  2061. X#ifdef MAILCALL
  2062. X    setmail();
  2063. X#endif
  2064. X    setdfltcmd();
  2065. X#ifdef CLEAREOL
  2066. X    if (erase_screen && can_home_clear)
  2067. X        clear_rest();
  2068. X#endif /* CLEAREOL */
  2069. X    unflush_output();        /* disable any ^O in effect */
  2070. X    standout();            /* enter standout mode */
  2071. X    printf(prompt,mailcall,dfltcmd);/* print prompt, whatever it is */
  2072. X    un_standout();            /* leave standout mode */
  2073. X    putchar(' ');
  2074. X    fflush(stdout);
  2075. reinp_article:
  2076. X    reread = FALSE;
  2077. X    forcelast = FALSE;
  2078. X    eat_typeahead();
  2079. X#ifdef PENDING
  2080. X    look_ahead();            /* see what we can do in advance */
  2081. X    if (!input_pending())
  2082. X        collect_subjects();        /* loads subject cache until */
  2083. X                    /* input is pending */
  2084. X#endif
  2085. X    getcmd(buf);
  2086. X    if (errno || *buf == '\f') {
  2087. X        if (LINES < 100 && !int_count)
  2088. X        *buf = '\f';        /* on CONT fake up refresh */
  2089. X        else {
  2090. X        putchar('\n') FLUSH;        /* but only on a crt */
  2091. X        goto reask_article;
  2092. X        }
  2093. X    }
  2094. article_level:
  2095. X#ifdef USETHREADS
  2096. X    output_chase_phrase = TRUE;
  2097. X#endif
  2098. X
  2099. X    /* parse and process article level command */
  2100. X
  2101. X    switch (art_switch()) {
  2102. X    case AS_INP:            /* multichar command rubbed out */
  2103. X        goto reinp_article;
  2104. X    case AS_ASK:            /* reprompt "End of article..." */
  2105. X        goto reask_article;
  2106. X    case AS_CLEAN:            /* exit newsgroup */
  2107. X        goto cleanup;
  2108. X    case AS_NORM:            /* display article art */
  2109. X        break;
  2110. X    }
  2111. X    }                    /* end of article selection loop */
  2112. X    
  2113. X/* shut down newsgroup */
  2114. X
  2115. cleanup:
  2116. X    decode_end();
  2117. X#ifdef KILLFILES
  2118. X    kill_unwanted(firstart,"\nCleaning up...\n\n",FALSE);
  2119. X                    /* do cleanup from KILL file, if any */
  2120. X#endif
  2121. X#ifdef USETHREADS
  2122. X    if (ThreadedGroup)
  2123. X    unuse_data(0);            /* free article thread data */
  2124. X#endif
  2125. X    in_ng = FALSE;            /* leave newsgroup state */
  2126. X    if (artfp != Nullfp) {        /* article still open? */
  2127. X    fclose(artfp);            /* close it */
  2128. X    artfp = Nullfp;            /* and tell the world */
  2129. X#ifdef SERVER
  2130. X        interp(intrpwork,MAXFILENAME-1, "%P");
  2131. X        sprintf(artname, "%s/rrn%ld.%ld", intrpwork,(long) openart, our_pid);
  2132. X        UNLINK(artname);
  2133. X#endif /* SERVER */
  2134. X    openart = 0;
  2135. X    }
  2136. X    putchar('\n') FLUSH;
  2137. X#ifdef DELAYMARK
  2138. X    yankback();                /* do a Y command */
  2139. X#endif
  2140. X    restore_ng();            /* reconstitute .newsrc line */
  2141. X    doing_ng = FALSE;            /* tell sig_catcher to cool it */
  2142. X    free(ctlarea);            /* return the control area */
  2143. X#ifdef CACHESUBJ
  2144. X    if (subj_list) {
  2145. X    for (i=OFFSET(lastart); i>=0; --i)
  2146. X        if (subj_list[i])
  2147. X        free(subj_list[i]);
  2148. X#ifndef lint
  2149. X    free((char*)subj_list);
  2150. X#endif /* lint */
  2151. X    }
  2152. X#endif
  2153. X    write_rc();                /* and update .newsrc */
  2154. X    rc_changed = FALSE;            /* tell sig_catcher it is ok */
  2155. X    if (chdir(spool)) {
  2156. X    printf(nocd,spool) FLUSH;
  2157. X    sig_catcher(0);
  2158. X    }
  2159. X#ifdef KILLFILES
  2160. X    if (localkfp) {
  2161. X    fclose(localkfp);
  2162. X    localkfp = Nullfp;
  2163. X    }
  2164. X#endif
  2165. X    mode = oldmode;
  2166. X    return exit_code;
  2167. X}                    /* Whew! */
  2168. X
  2169. X/* decide what to do at the end of an article */
  2170. X
  2171. int
  2172. art_switch()
  2173. X{
  2174. X    register ART_NUM i;
  2175. X      
  2176. X    setdef(buf,dfltcmd);
  2177. X#ifdef VERIFY
  2178. X    printcmd();
  2179. X#endif
  2180. X
  2181. X    switch (*buf) {
  2182. X#ifdef USETHREADS
  2183. X    case '<':            /* goto previous thread */
  2184. X    if (!ThreadedGroup) {
  2185. X        goto group_unthreaded;
  2186. X    }
  2187. X    prev_root();
  2188. X    return AS_NORM;
  2189. X    case '>':            /* goto next thread */
  2190. X    if (!ThreadedGroup) {
  2191. X        goto group_unthreaded;
  2192. X    }
  2193. X    next_root();
  2194. X    return AS_NORM;
  2195. X    case 'U': {            /* unread some articles */
  2196. X    char *u_prompt, *u_help_thread;
  2197. X
  2198. X    if (!ThreadedGroup) {
  2199. X        dfltcmd = "a";
  2200. X        u_help_thread = nullstr;
  2201. X#ifdef VERBOSE
  2202. X        IF(verbose)
  2203. X        u_prompt = "\nSet unread: all articles? [an] ";
  2204. X        ELSE
  2205. X#endif
  2206. X#ifdef TERSE
  2207. X        u_prompt = "\nUnread? [an] ";
  2208. X#endif
  2209. X    }
  2210. X    else if (!p_art || art > lastart) {
  2211. X        dfltcmd = "+";
  2212. X        u_help_thread = nullstr;
  2213. X#ifdef VERBOSE
  2214. X        IF(verbose)
  2215. X        u_prompt = "\nSet unread: +select or all? [+an] ";
  2216. X        ELSE
  2217. X#endif
  2218. X#ifdef TERSE
  2219. X        u_prompt = "\nUnread? [+an] ";
  2220. X#endif
  2221. X    }
  2222. X    else {
  2223. X        dfltcmd = "+";
  2224. X#ifdef VERBOSE
  2225. X        IF(verbose) {
  2226. X        u_prompt = "\n\
  2227. Set unread: +select, thread, subthread, or all? [+tsan] ";
  2228. X        u_help_thread = "\
  2229. Type t or SP to mark this thread's articles as unread.\n\
  2230. Type s to mark the current article and its descendants as unread.\n";
  2231. X        }
  2232. X        ELSE
  2233. X#endif
  2234. X#ifdef TERSE
  2235. X        {
  2236. X        u_prompt = "\nUnread? [ts+an] ";
  2237. X        u_help_thread = "\
  2238. t or SP to mark thread unread.\n\
  2239. s to mark subthread unread.\n";
  2240. X        }
  2241. X#endif
  2242. X    }
  2243. X      reask_unread:
  2244. X    in_char(u_prompt,'u');
  2245. X    setdef(buf,dfltcmd);
  2246. X#ifdef VERIFY
  2247. X    printcmd();
  2248. X#endif
  2249. X    putchar('\n') FLUSH;
  2250. X    if (*buf == 'h') {
  2251. X#ifdef VERBOSE
  2252. X        IF(verbose)
  2253. X        {
  2254. X        if (ThreadedGroup)
  2255. X            fputs("\
  2256. Type + to enter select thread mode using all the unread articles.\n\
  2257. X(The selected threads will be marked as unread and displayed as usual.)\n\
  2258. X",stdout) FLUSH;
  2259. X        fputs(u_help_thread,stdout);
  2260. X        fputs("\
  2261. Type a to mark all articles in this group as unread.\n\
  2262. Type n to change nothing.\n\
  2263. X",stdout) FLUSH;
  2264. X        }
  2265. X        ELSE
  2266. X#endif
  2267. X#ifdef TERSE
  2268. X        {
  2269. X        if (ThreadedGroup)
  2270. X            fputs("\
  2271. X+ to select threads from the unread.\n\
  2272. X",stdout) FLUSH;
  2273. X        fputs(u_help_thread,stdout);
  2274. X        fputs("\
  2275. a to mark all articles unread.\n\
  2276. n to change nothing.\n\
  2277. X",stdout) FLUSH;
  2278. X        }
  2279. X#endif
  2280. X        goto reask_unread;
  2281. X    }
  2282. X    else if (*buf == 'n' || *buf == 'q') {
  2283. X        return AS_ASK;
  2284. X    }
  2285. X    else if (*buf == 't' && u_help_thread != nullstr)
  2286. X        follow_thread('u');
  2287. X    else if (*buf == 's' && u_help_thread != nullstr)
  2288. X        follow_thread('U');
  2289. X    else if (*buf == 'a') {
  2290. X        check_first(absfirst);
  2291. X        for (i = absfirst; i <= lastart; i++) {
  2292. X        onemore(i);        /* mark as unread */
  2293. X        }
  2294. X        scan_all_roots = FALSE;
  2295. X        count_roots(FALSE);
  2296. X        if (art > lastart) {
  2297. X        first_art();
  2298. X        }
  2299. X    }
  2300. X    else if (ThreadedGroup && *buf == '+') {
  2301. X        *buf = 'U';
  2302. X        goto select_threads;
  2303. X    }
  2304. X    else {
  2305. X        fputs(hforhelp,stdout) FLUSH;
  2306. X        settle_down();
  2307. X        goto reask_unread;
  2308. X    }
  2309. X    return AS_NORM;
  2310. X    }
  2311. X    case '[':            /* goto parent article */
  2312. X    case '{':            /* goto thread's root article */
  2313. X    if (p_art) {
  2314. X        if (!p_art->parent) {
  2315. X        if (p_art == p_articles + p_roots[p_art->root].articles) {
  2316. X            register char *cp = (*buf=='['?"parent":"root");
  2317. X#ifdef VERBOSE
  2318. X            IF(verbose)
  2319. X            fprintf(stdout,"\n\
  2320. There is no %s article prior to this one.\n",cp) FLUSH;
  2321. X            ELSE
  2322. X#endif
  2323. X#ifdef TERSE
  2324. X            fprintf(stdout,"\nNo prior %s.\n",cp) FLUSH;
  2325. X#endif
  2326. X            return AS_ASK;
  2327. X        }
  2328. X        *buf = '{';
  2329. X        p_art--;
  2330. X        }
  2331. X        else
  2332. X        p_art += p_art->parent;
  2333. X
  2334. X        if (*buf == '{')
  2335. X        while (p_art->parent)
  2336. X            p_art += p_art->parent;
  2337. X
  2338. X        art = p_art->num;
  2339. X        reread = TRUE;
  2340. X        return AS_NORM;
  2341. X    }
  2342. not_threaded:
  2343. X    if (ThreadedGroup) {
  2344. X#ifdef VERBOSE
  2345. X        IF(verbose)
  2346. X        fputs("\nThis article is not threaded.\n",stdout) FLUSH;
  2347. X        ELSE
  2348. X#endif
  2349. X#ifdef TERSE
  2350. X        fputs("\nUnthreaded article.\n",stdout) FLUSH;
  2351. X#endif
  2352. X        return AS_ASK;
  2353. X    }
  2354. group_unthreaded:
  2355. X#ifdef VERBOSE
  2356. X    IF(verbose)
  2357. X        fputs("\nThis group is not threaded.\n",stdout) FLUSH;
  2358. X    ELSE
  2359. X#endif
  2360. X#ifdef TERSE
  2361. X        fputs("\nUnthreaded group.\n",stdout) FLUSH;
  2362. X#endif
  2363. X    return AS_ASK;
  2364. X    case ']':            /* goto child article */
  2365. X    case '}':            /* goto thread's leaf article */
  2366. X    if (p_art) {
  2367. X        if (!(p_art++)->child_cnt) {
  2368. X        PACKED_ARTICLE *root_limit = upper_limit(p_art-1,FALSE);
  2369. X
  2370. X        if (p_art == root_limit) {
  2371. X#ifdef VERBOSE
  2372. X            IF(verbose)
  2373. X            fputs("\n\
  2374. This is the last leaf in this tree.\n",stdout) FLUSH;
  2375. X            ELSE
  2376. X#endif
  2377. X#ifdef TERSE
  2378. X            fputs("\nLast leaf.\n",stdout) FLUSH;
  2379. X#endif
  2380. X            p_art--;
  2381. X            return AS_ASK;
  2382. X        }
  2383. X        if (*buf == ']')
  2384. X            *buf = '}';
  2385. X        else {
  2386. X            while (++p_art != root_limit && p_art->parent)
  2387. X            ;
  2388. X            p_art--;
  2389. X            *buf = ' ';
  2390. X        }
  2391. X        }
  2392. X        if (*buf == '}')
  2393. X        while (p_art->child_cnt)
  2394. X            p_art++;
  2395. X
  2396. X        art = p_art->num;
  2397. X        reread = TRUE;
  2398. X        return AS_NORM;
  2399. X    }
  2400. X    goto not_threaded;
  2401. X    case 'T':
  2402. X    if (p_art) {
  2403. X        sprintf(buf,"T%ld\t# %s",(long)p_roots[p_art->root].root_num,
  2404. X        subject_ptrs[p_art->subject]);
  2405. X        fputs(buf,stdout);
  2406. X        kf_append(buf);
  2407. X        follow_thread('J');
  2408. X        return AS_NORM;
  2409. X    }
  2410. X    goto not_threaded;
  2411. X    case 'K':
  2412. X    if (p_art) {
  2413. X        /* first, write kill-subject command */
  2414. X        (void)art_search(buf, (sizeof buf), TRUE);
  2415. X        art = curr_art;
  2416. X        p_art = curr_p_art;
  2417. X        follow_thread('k');        /* then take care of any prior subjs */
  2418. X        return AS_NORM;
  2419. X    }
  2420. X    goto normal_search;
  2421. X    case ',':        /* kill this node and all descendants */
  2422. X    mark_as_read();
  2423. X    *buf = 'K';
  2424. X    case 'k':        /* kill current subject # (e.g. [1]) */
  2425. X    case 'J':        /* Junk all nodes in this thread */
  2426. X    if (ThreadedGroup) {
  2427. X        follow_thread(*buf);
  2428. X        return AS_NORM;
  2429. X    }
  2430. X    *buf = 'k';
  2431. X    goto normal_search;
  2432. X    case 't':
  2433. X    carriage_return();
  2434. X#ifndef CLEAREOL
  2435. X    erase_eol();        /* erase the prompt */
  2436. X#else
  2437. X    if (erase_screen && can_home_clear)
  2438. X        clear_rest();
  2439. X    else
  2440. X        erase_eol();    /* erase the prompt */
  2441. X#endif /* CLEAREOL */
  2442. X    fflush(stdout);
  2443. X    page_line = 1;
  2444. X    p_art = curr_p_art;
  2445. X    entire_tree();
  2446. X    return AS_ASK;
  2447. X    case ':':            /* execute command on selected articles */
  2448. X    if (!ThreadedGroup) {
  2449. X        goto group_unthreaded;
  2450. X    }
  2451. X    page_line = 1;
  2452. X    if (!use_selected())
  2453. X        return AS_INP;
  2454. X    putchar('\n');
  2455. X    art = curr_art;
  2456. X    p_art = curr_p_art;
  2457. X    return AS_ASK;
  2458. X#endif /* USETHREADS */
  2459. X    case 'p':            /* find previous unread article */
  2460. X#ifdef USETHREADS
  2461. X    if (ThreadedGroup) {
  2462. X        goto backtrack_threads;
  2463. X    }
  2464. X#endif
  2465. X    do {
  2466. X        if (art <= firstart)
  2467. X        break;
  2468. X        art--;
  2469. X#ifdef SERVER
  2470. X    } while (was_read(art) || nntpopen(art,GET_HEADER) == Nullfp);
  2471. X#else
  2472. X    } while (was_read(art) || artopen(art) == Nullfp);
  2473. X#endif
  2474. X#ifdef ARTSEARCH
  2475. X    srchahead = 0;
  2476. X#endif
  2477. X    return AS_NORM;
  2478. X    case 'P':        /* goto previous article */
  2479. X#ifdef USETHREADS
  2480. X    if (ThreadedGroup) {
  2481. backtrack_threads:
  2482. X        backtrack_thread(*buf);
  2483. X        art++;        /* prepare for art-- below */
  2484. X    }
  2485. X#endif
  2486. X    if (art > absfirst)
  2487. X        art--;
  2488. X    else {
  2489. X#ifdef VERBOSE
  2490. X        IF(verbose)
  2491. X        fprintf(stdout,"\n\
  2492. There are no%s articles prior to this one.\n\
  2493. X",*buf=='P'?nullstr:" unread") FLUSH;
  2494. X        ELSE
  2495. X#endif
  2496. X#ifdef TERSE
  2497. X        fprintf(stdout,"\n\
  2498. No previous%s articles\n\
  2499. X",*buf=='P'?nullstr:" unread") FLUSH;
  2500. X#endif
  2501. X        art = curr_art;
  2502. X#ifdef USETHREADS
  2503. X        p_art = curr_p_art;
  2504. X#endif
  2505. X        return AS_ASK;
  2506. X    }
  2507. X    reread = TRUE;
  2508. X#ifdef ARTSEARCH
  2509. X    srchahead = 0;
  2510. X#endif
  2511. X    return AS_NORM;
  2512. X    case '-':
  2513. X    if (recent_art >= 0) {
  2514. X#ifdef USETHREADS
  2515. X        p_art = recent_p_art;
  2516. X#endif
  2517. X        art = recent_art;
  2518. X        reread = TRUE;
  2519. X#ifdef ARTSEARCH
  2520. X        srchahead = -(srchahead != 0);
  2521. X#endif
  2522. X        return AS_NORM;
  2523. X    }
  2524. X    else {
  2525. X        exit_code = NG_MINUS;
  2526. X        return AS_CLEAN;
  2527. X    }
  2528. X    case 'n':        /* find next unread article? */
  2529. X#ifdef USETHREADS
  2530. X    if (ThreadedGroup) {
  2531. X        follow_thread(*buf);
  2532. X        return AS_NORM;
  2533. X    }
  2534. X#endif
  2535. X    if (art > lastart) {
  2536. X        if (!toread[ng])
  2537. X        return AS_CLEAN;
  2538. X        art = firstart;
  2539. X    }
  2540. X#ifdef ARTSEARCH
  2541. X    else if (scanon && srchahead) {
  2542. X        *buf = Ctl('n');
  2543. X        goto normal_search;
  2544. X    }
  2545. X#endif
  2546. X    else
  2547. X        art++;
  2548. X
  2549. X#ifdef ARTSEARCH
  2550. X    srchahead = 0;
  2551. X#endif
  2552. X    return AS_NORM;
  2553. X    case 'N':            /* goto next article */
  2554. X#ifdef USETHREADS
  2555. X    if (ThreadedGroup) {
  2556. X        follow_thread(*buf);
  2557. X        return AS_NORM;
  2558. X    }
  2559. X#endif
  2560. X    if (art > lastart)
  2561. X        art = absfirst;
  2562. X    else
  2563. X        art++;
  2564. X    if (art <= lastart)
  2565. X        reread = TRUE;
  2566. X#ifdef ARTSEARCH
  2567. X    srchahead = 0;
  2568. X#endif
  2569. X    return AS_NORM;
  2570. X    case '$':
  2571. X    art = lastart+1;
  2572. X    forcelast = TRUE;
  2573. X#ifdef USETHREADS
  2574. X    p_art = Nullart;
  2575. X#endif
  2576. X#ifdef ARTSEARCH
  2577. X    srchahead = 0;
  2578. X#endif
  2579. X    return AS_NORM;
  2580. X    case '1': case '2': case '3':    /* goto specified article */
  2581. X    case '4': case '5': case '6':    /* or do something with a range */
  2582. X    case '7': case '8': case '9': case '.':
  2583. X    forcelast = TRUE;
  2584. X    switch (numnum()) {
  2585. X    case NN_INP:
  2586. X        return AS_INP;
  2587. X    case NN_ASK:
  2588. X        return AS_ASK;
  2589. X    case NN_REREAD:
  2590. X        reread = TRUE;
  2591. X#ifdef ARTSEARCH
  2592. X        if (srchahead)
  2593. X        srchahead = -1;
  2594. X#endif
  2595. X        break;
  2596. X    case NN_NORM:
  2597. X        if (was_read(art)) {
  2598. X#ifdef USETHREADS
  2599. X        first_art();
  2600. X#else
  2601. X        art = firstart;
  2602. X#endif
  2603. X        pad(just_a_sec/3);
  2604. X        }
  2605. X        else {
  2606. X        putchar('\n');
  2607. X        return AS_ASK;
  2608. X        }
  2609. X        break;
  2610. X    }
  2611. X    return AS_NORM;
  2612. X    case Ctl('k'):
  2613. X    edit_kfile();
  2614. X    return AS_ASK;
  2615. X#ifndef USETHREADS
  2616. X    case 'K':
  2617. X    case 'k':
  2618. X#endif
  2619. X    case Ctl('n'):    /* search for next article with same subject */
  2620. X#ifdef USETHREADS
  2621. X    if (ThreadedGroup) {
  2622. X        follow_thread(*buf);
  2623. X        return AS_NORM;
  2624. X    }
  2625. X#endif
  2626. X    case Ctl('p'):    /* search for previous article with same subject */
  2627. X#ifdef USETHREADS
  2628. X    if (ThreadedGroup) {
  2629. X        goto backtrack_threads;
  2630. X    }
  2631. X#endif
  2632. X    case '/': case '?':
  2633. normal_search:
  2634. X#ifdef ARTSEARCH
  2635. X    {        /* search for article by pattern */
  2636. X    char cmd = *buf;
  2637. X    
  2638. X    reread = TRUE;        /* assume this */
  2639. X    page_line = 1;
  2640. X    switch (art_search(buf, (sizeof buf), TRUE)) {
  2641. X    case SRCH_ERROR:
  2642. X        art = curr_art;
  2643. X        return AS_ASK;
  2644. X    case SRCH_ABORT:
  2645. X        art = curr_art;
  2646. X        return AS_INP;
  2647. X    case SRCH_INTR:
  2648. X#ifdef VERBOSE
  2649. X        IF(verbose)
  2650. X        printf("\n(Interrupted at article %ld)\n",(long)art) FLUSH;
  2651. X        ELSE
  2652. X#endif
  2653. X#ifdef TERSE
  2654. X        printf("\n(Intr at %ld)\n",(long)art) FLUSH;
  2655. X#endif
  2656. X        art = curr_art;
  2657. X                /* restore to current article */
  2658. X        return AS_ASK;
  2659. X    case SRCH_DONE:
  2660. X        fputs("done\n",stdout) FLUSH;
  2661. X        pad(just_a_sec/3);    /* 1/3 second */
  2662. X        if (!srchahead) {
  2663. X        art = curr_art;
  2664. X        return AS_ASK;
  2665. X        }
  2666. X#ifdef USETHREADS
  2667. X        first_art();
  2668. X#else
  2669. X        art = firstart;
  2670. X#endif
  2671. X        reread = FALSE;
  2672. X        return AS_NORM;
  2673. X    case SRCH_SUBJDONE:
  2674. X#ifdef UNDEF
  2675. X        fputs("\n\n\n\nSubject not found.\n",stdout) FLUSH;
  2676. X        pad(just_a_sec/3);    /* 1/3 second */
  2677. X#endif
  2678. X#ifdef USETHREADS
  2679. X        first_art();
  2680. X#else
  2681. X        art = firstart;
  2682. X#endif
  2683. X        reread = FALSE;
  2684. X        return AS_NORM;
  2685. X    case SRCH_NOTFOUND:
  2686. X        fputs("\n\n\n\nNot found.\n",stdout) FLUSH;
  2687. X        art = curr_art;  /* restore to current article */
  2688. X        return AS_ASK;
  2689. X    case SRCH_FOUND:
  2690. X        if (cmd == Ctl('n') || cmd == Ctl('p'))
  2691. X        oldsubject = TRUE;
  2692. X        break;
  2693. X    }
  2694. X    return AS_NORM;
  2695. X    }
  2696. X#else
  2697. X    buf[1] = '\0';
  2698. X    notincl(buf);
  2699. X    return AS_ASK;
  2700. X#endif
  2701. X    case 'u':            /* unsubscribe from this newsgroup? */
  2702. X    rcchar[ng] = NEGCHAR;
  2703. X    return AS_CLEAN;
  2704. X    case 'M':
  2705. X#ifdef DELAYMARK
  2706. X    if (art <= lastart) {
  2707. X        delay_unmark(art);
  2708. X        printf("\nArticle %ld will return.\n",(long)art) FLUSH;
  2709. X    }
  2710. X#else
  2711. X    notincl("M");
  2712. X#endif
  2713. X    return AS_ASK;
  2714. X    case 'm':
  2715. X    if (art <= lastart) {
  2716. X        unmark_as_read();
  2717. X        printf("\nArticle %ld marked as still unread.\n",(long)art) FLUSH;
  2718. X    }
  2719. X    return AS_ASK;
  2720. X    case 'c':            /* catch up */
  2721. X      reask_catchup:
  2722. X#ifdef VERBOSE
  2723. X    IF(verbose)
  2724. X        in_char("\nDo you really want to mark everything as read? [yn] ",
  2725. X        'C');
  2726. X    ELSE
  2727. X#endif
  2728. X#ifdef TERSE
  2729. X        in_char("\nReally? [ynh] ", 'C');
  2730. X#endif
  2731. X    setdef(buf,"y");
  2732. X#ifdef VERIFY
  2733. X    printcmd();
  2734. X#endif
  2735. X    putchar('\n') FLUSH;
  2736. X    if (*buf == 'h') {
  2737. X#ifdef VERBOSE
  2738. X        IF(verbose)
  2739. X        fputs("\
  2740. Type y or SP to mark all articles as read.\n\
  2741. Type n to leave articles marked as they are.\n\
  2742. Type u to mark everything read and unsubscribe.\n\
  2743. X",stdout) FLUSH;
  2744. X        ELSE
  2745. X#endif
  2746. X#ifdef TERSE
  2747. X        fputs("\
  2748. y or SP to mark all read.\n\
  2749. n to forget it.\n\
  2750. u to mark all and unsubscribe.\n\
  2751. X",stdout) FLUSH;
  2752. X#endif
  2753. X        goto reask_catchup;
  2754. X    }
  2755. X    else if (*buf == 'n' || *buf == 'q') {
  2756. X        return AS_ASK;
  2757. X    }
  2758. X    else if (*buf != 'y' && *buf != 'u') {
  2759. X        fputs(hforhelp,stdout) FLUSH;
  2760. X        settle_down();
  2761. X        goto reask_catchup;
  2762. X    }
  2763. X    for (i = firstart; i <= lastart; i++) {
  2764. X        oneless(i);        /* mark as read */
  2765. X    }
  2766. X#ifdef USETHREADS
  2767. X    selected_count = selected_root_cnt = unthreaded = 0;
  2768. X#endif
  2769. X#ifdef DELAYMARK
  2770. X    if (dmfp)
  2771. X        yankback();
  2772. X#endif
  2773. X    if (*buf == 'u') {
  2774. X        rcchar[ng] = NEGCHAR;
  2775. X        return AS_CLEAN;
  2776. X    }
  2777. X#ifdef USETHREADS
  2778. X    p_art = Nullart;
  2779. X#endif
  2780. X    art = lastart+1;
  2781. X    forcelast = FALSE;
  2782. X    return AS_NORM;
  2783. X    case 'Q':
  2784. X    exit_code = NG_ASK;
  2785. X    /* FALL THROUGH */
  2786. X    case 'q':            /* go back up to newsgroup level? */
  2787. X    return AS_CLEAN;
  2788. X    case 'j':
  2789. X    putchar('\n') FLUSH;
  2790. X    if (art <= lastart)
  2791. X        mark_as_read();
  2792. X    return AS_ASK;
  2793. X    case 'h': {            /* help? */
  2794. X    int cmd;
  2795. X
  2796. X    if ((cmd = help_art()) > 0)
  2797. X        pushchar(cmd);
  2798. X    return AS_ASK;
  2799. X    }
  2800. X    case '&':
  2801. X    if (switcheroo()) /* get rest of command */
  2802. X        return AS_INP;    /* if rubbed out, try something else */
  2803. X    return AS_ASK;
  2804. X    case '#':
  2805. X#ifdef VERBOSE
  2806. X    IF(verbose)
  2807. X        printf("\nThe last article is %ld.\n",(long)lastart) FLUSH;
  2808. X    ELSE
  2809. X#endif
  2810. X#ifdef TERSE
  2811. X        printf("\n%ld\n",(long)lastart) FLUSH;
  2812. X#endif
  2813. X    return AS_ASK;
  2814. X#ifdef USETHREADS
  2815. X    case '+':            /* enter thread selection mode */
  2816. X    if (ThreadedGroup) {
  2817. select_threads:
  2818. X        *buf = select_thread(*buf);
  2819. X        switch (*buf) {
  2820. X        case '+':
  2821. X        case '\033':
  2822. X        putchar('\n') FLUSH;
  2823. X        return AS_ASK;
  2824. X        case 'Q':
  2825. X        exit_code = NG_ASK;
  2826. X        /* FALL THROUGH */
  2827. X        case 'q':
  2828. X        break;
  2829. X        case 'N':
  2830. X        exit_code = NG_SELNEXT;
  2831. X        break;
  2832. X        case 'P':
  2833. X        exit_code = NG_SELPRIOR;
  2834. X        break;
  2835. X        default:
  2836. X        if (toread[ng])
  2837. X            return AS_NORM;
  2838. X        break;
  2839. X        }
  2840. X        return AS_CLEAN;
  2841. X    }
  2842. X    /* FALL THROUGH */
  2843. X#endif
  2844. X    case '=': {            /* list subjects */
  2845. X    char tmpbuf[256];
  2846. X    ART_NUM oldart = art;
  2847. X    int cmd;
  2848. X    char *subjline = getval("SUBJLINE",Nullch);
  2849. X#ifndef CACHESUBJ
  2850. X    char *s;
  2851. X#endif
  2852. X
  2853. X    page_init();
  2854. X#ifdef CACHESUBJ
  2855. X    if (!subj_list)
  2856. X        fetchsubj(art,TRUE,FALSE);
  2857. X#endif
  2858. X    for (i=firstart; i<=lastart && !int_count; i++) {
  2859. X#ifdef CACHESUBJ
  2860. X        if (!was_read(i) &&
  2861. X          (subj_list[OFFSET(i)] != Nullch || fetchsubj(i,FALSE,FALSE)) &&
  2862. X          *subj_list[OFFSET(i)] ) {
  2863. X        sprintf(tmpbuf,"%5ld ", i);
  2864. X        if (subjline) {
  2865. X            art = i;
  2866. X            interp(tmpbuf + 6, (sizeof tmpbuf) - 6, subjline);
  2867. X        }
  2868. X        else
  2869. X            safecpy(tmpbuf + 6, subj_list[OFFSET(i)],
  2870. X            (sizeof tmpbuf) - 6);
  2871. X        if (cmd = print_lines(tmpbuf,NOMARKING)) {
  2872. X            if (cmd > 0)
  2873. X            pushchar(cmd);
  2874. X            break;
  2875. X        }
  2876. X        }
  2877. X#else
  2878. X        if (!was_read(i) && (s = fetchsubj(i,FALSE,FALSE)) && *s) {
  2879. X        sprintf(tmpbuf,"%5ld ", i);
  2880. X        if (subjline) {    /* probably fetches it again! */
  2881. X            art = i;
  2882. X            interp(tmpbuf + 6, (sizeof tmpbuf) - 6, subjline);
  2883. X        }
  2884. X        else
  2885. X            safecpy(tmpbuf + 6, s, (sizeof tmpbuf) - 6);
  2886. X        if (cmd = print_lines(tmpbuf,NOMARKING)) {
  2887. X            if (cmd > 0)
  2888. X            pushchar(cmd);
  2889. X            break;
  2890. X        }
  2891. X        }
  2892. X#endif
  2893. X    }
  2894. X    int_count = 0;
  2895. X    art = oldart;
  2896. X    return AS_ASK;
  2897. X    }
  2898. X    case '^':
  2899. X#ifdef USETHREADS
  2900. X    first_art();
  2901. X#else
  2902. X    art = firstart;
  2903. X#endif
  2904. X#ifdef ARTSEARCH
  2905. X    srchahead = 0;
  2906. X#endif
  2907. X    return AS_NORM;
  2908. X#if defined(CACHESUBJ) && defined(DEBUGGING)
  2909. X    case 'D':
  2910. X    printf("\nFirst article: %ld\n",(long)firstart) FLUSH;
  2911. X    if (!subj_list)
  2912. X        fetchsubj(art,TRUE,FALSE);
  2913. X    if (subj_list != Null(char **)) {
  2914. X        for (i=1; i<=lastart && !int_count; i++) {
  2915. X        if (subj_list[OFFSET(i)])
  2916. X            printf("%5ld %c %s\n",
  2917. X            i, (was_read(i)?'y':'n'), subj_list[OFFSET(i)]) FLUSH;
  2918. X        }
  2919. X    }
  2920. X    int_count = 0;
  2921. X    return AS_ASK;
  2922. X#endif
  2923. X    case 'v':
  2924. X    if (art <= lastart) {
  2925. X        reread = TRUE;
  2926. X        do_hiding = FALSE;
  2927. X    }
  2928. X    return AS_NORM;
  2929. X#ifdef ROTATION
  2930. X    case Ctl('x'):
  2931. X#endif
  2932. X    case Ctl('r'):
  2933. X#ifdef ROTATION
  2934. X    rotate = (*buf==Ctl('x'));
  2935. X#endif
  2936. X    if (art <= lastart)
  2937. X        reread = TRUE;
  2938. X    else
  2939. X        forcelast = TRUE;
  2940. X    return AS_NORM;
  2941. X#ifdef ROTATION
  2942. X    case 'X':
  2943. X    rotate = !rotate;
  2944. X    /* FALL THROUGH */
  2945. X#else
  2946. X    case Ctl('x'):
  2947. X    case 'x':
  2948. X    case 'X':
  2949. X    notincl("x");
  2950. X    return AS_ASK;
  2951. X#endif
  2952. X    case 'l': case Ctl('l'):        /* refresh screen */
  2953. X    if (art <= lastart) {
  2954. X        reread = TRUE;
  2955. X        clear();
  2956. X        do_fseek = TRUE;
  2957. X        artline = topline;
  2958. X        if (artline < 0)
  2959. X        artline = 0;
  2960. X    }
  2961. X    return AS_NORM;
  2962. X    case 'b': case Ctl('b'):        /* back up a page */
  2963. X    if (art <= lastart) {
  2964. X        ART_LINE target;
  2965. X
  2966. X        reread = TRUE;
  2967. X        clear();
  2968. X        do_fseek = TRUE;
  2969. X        target = topline - (LINES - 2);
  2970. X        artline = topline;
  2971. X        if (artline >= 0) do {
  2972. X        artline--;
  2973. X        } while(artline >= 0 && artline > target && vrdary(artline-1) >= 0);
  2974. X        topline = artline;
  2975. X        if (artline < 0)
  2976. X        artline = 0;
  2977. X    }
  2978. X    return AS_NORM;
  2979. X    case '!':            /* shell escape */
  2980. X    if (escapade())
  2981. X        return AS_INP;
  2982. X    return AS_ASK;
  2983. X    case 'C': {
  2984. X    cancel_article();
  2985. X    return AS_ASK;
  2986. X    }
  2987. X    case 'z': {
  2988. X    supersede_article();    /* supersedes */
  2989. X    return AS_ASK;
  2990. X    }
  2991. X    case 'R':
  2992. X    case 'r': {            /* reply? */
  2993. X    reply();
  2994. X    return AS_ASK;
  2995. X    }
  2996. X    case 'F':
  2997. X    case 'f': {            /* followup command */
  2998. X    followup();
  2999. X    forcegrow = TRUE;        /* recalculate lastart */
  3000. X    return AS_ASK;
  3001. X    }
  3002. X    case '|':
  3003. X    case 'w': case 'W':
  3004. X    case 's': case 'S':        /* save command */
  3005. X    case 'e':            /* extract command */
  3006. X    if (save_article() == SAVE_ABORT)
  3007. X        return AS_INP;
  3008. X    int_count = 0;
  3009. X    return AS_ASK;
  3010. X    case 'E':
  3011. X    if (decode_fp)
  3012. X        decode_end();
  3013. X    else
  3014. X        putchar('\n') FLUSH;
  3015. X    return AS_ASK;
  3016. X#ifdef DELAYMARK
  3017. X    case 'Y':                /* yank back M articles */
  3018. X    yankback();
  3019. X#ifdef USETHREADS
  3020. X    first_art();
  3021. X#else
  3022. X    art = firstart;            /* from the beginning */
  3023. X#endif
  3024. X    return AS_NORM;            /* pretend nothing happened */
  3025. X#endif
  3026. X#ifdef STRICTCR
  3027. X    case '\n':
  3028. X    fputs(badcr,stdout) FLUSH;
  3029. X    return AS_ASK;
  3030. X#endif
  3031. X    default:
  3032. X    printf("\n%s",hforhelp) FLUSH;
  3033. X    settle_down();
  3034. X    return AS_ASK;
  3035. X    }
  3036. X}
  3037. X
  3038. X#ifdef MAILCALL
  3039. X/* see if there is any mail */
  3040. X
  3041. void
  3042. setmail()
  3043. X{
  3044. X    if (! (mailcount++)) {
  3045. X    char *mailfile = filexp(getval("MAILFILE",MAILFILE));
  3046. X    
  3047. X    if (stat(mailfile,&filestat) < 0 || !filestat.st_size
  3048. X        || filestat.st_atime > filestat.st_mtime)
  3049. X        mailcall = nullstr;
  3050. X    else
  3051. X        mailcall = getval("MAILCALL","(Mail) ");
  3052. X    }
  3053. X    mailcount %= 10;            /* check every 10 articles */
  3054. X}
  3055. X#endif
  3056. X
  3057. void
  3058. setdfltcmd()
  3059. X{
  3060. X#ifdef USETHREADS
  3061. X    if (toread[ng] == unthreaded) {
  3062. X#else
  3063. X    if (!toread[ng]) {
  3064. X#endif
  3065. X    if (art > lastart)
  3066. X        dfltcmd = "qnp";
  3067. X    else
  3068. X        dfltcmd = "npq";
  3069. X    }
  3070. X    else {
  3071. X#ifdef ARTSEARCH
  3072. X# ifdef USETHREADS
  3073. X    if (!ThreadedGroup && srchahead)
  3074. X# else
  3075. X    if (srchahead)
  3076. X# endif
  3077. X        dfltcmd = "^Nnpq";
  3078. X    else
  3079. X#endif
  3080. X        dfltcmd = "npq";
  3081. X    }
  3082. X}
  3083. END_OF_FILE
  3084. if test 35048 -ne `wc -c <'ng.c'`; then
  3085.     echo shar: \"'ng.c'\" unpacked with wrong size!
  3086. fi
  3087. # end of 'ng.c'
  3088. fi
  3089. echo shar: End of archive 10 \(of 13\).
  3090. cp /dev/null ark10isdone
  3091. MISSING=""
  3092. for I in 1 2 3 4 5 6 7 8 9 10 11 12 13 ; do
  3093.     if test ! -f ark${I}isdone ; then
  3094.     MISSING="${MISSING} ${I}"
  3095.     fi
  3096. done
  3097. if test "${MISSING}" = "" ; then
  3098.     echo You have unpacked all 13 archives.
  3099.     rm -f ark[1-9]isdone ark[1-9][0-9]isdone
  3100. else
  3101.     echo You still need to unpack the following archives:
  3102.     echo "        " ${MISSING}
  3103. fi
  3104. ##  End of shell archive.
  3105. exit 0
  3106.